10

私の意見では、HtmlTextWriter を使用して HTML をレンダリングすることは非常に直感的ではありませんが、Web フォームに Web コントロールを実装する場合は、それを処理する必要があります。出力する HTML にもう少し似た、流暢なインターフェイスを作成することは可能かもしれないと考えました。これまでに思いついた構文について、人々がどう思うか知りたいです。

    public void Render(HtmlTextWriter writer)
    {
        writer
            .Tag(HtmlTextWriterTag.Div, e => e[HtmlTextWriterAttribute.Id, "id"][HtmlTextWriterAttribute.Name,"name"][HtmlTextWriterAttribute.Class,"class"])
                .Tag(HtmlTextWriterTag.Span)
                    .Text("Lorem")
                .EndTag()
                .Tag(HtmlTextWriterTag.Span)
                    .Text("ipsum")
                .EndTag()
            .EndTag();        
    }

"Tag"、"Text"、および "EndTag" は、HtmlTextWriter クラスの拡張メソッドであり、呼び出しを連鎖できるように、取り込んだインスタンスを返します。"Tag" の最初の呼び出しで使用されるオーバーロードで使用されるラムダに渡される引数は "HtmlAttributeManager" です。これは、HtmlTextWriterAttribute と文字列値を取り、インスタンスを返すインデクサーを提供するために HtmlTextWriter をラップする単純なクラスです。その呼び出しは連鎖できます。このクラスには、「Name」、「Class」、「Id」などの最も一般的な属性用のメソッドもあるため、上記の最初の呼び出しを次のように記述できます。

.Tag(HtmlTextWriterTag.Div, e => e.Id("id").Name("name").Class("class"))

少し長い例:

public void Render(HtmlTextWriter writer)
{
    writer
        .Tag(HtmlTextWriterTag.Div, a => a.Class("someClass", "someOtherClass"))
            .Tag(HtmlTextWriterTag.H1).Text("Lorem").EndTag()
            .Tag(HtmlTextWriterTag.Select, t => t.Id("fooSelect").Name("fooSelect").Class("selectClass"))
                .Tag(HtmlTextWriterTag.Option, t => t[HtmlTextWriterAttribute.Value, "1"][HtmlTextWriterAttribute.Title, "Selects the number 1."])
                    .Text("1")
                .EndTag(HtmlTextWriterTag.Option)
                .Tag(HtmlTextWriterTag.Option, t => t[HtmlTextWriterAttribute.Value, "2"][HtmlTextWriterAttribute.Title, "Selects the number 2."])
                    .Text("2")
                .EndTag(HtmlTextWriterTag.Option)
                .Tag(HtmlTextWriterTag.Option, t => t[HtmlTextWriterAttribute.Value, "3"][HtmlTextWriterAttribute.Title, "Selects the number 3."])
                    .Text("3")
                .EndTag(HtmlTextWriterTag.Option)
            .EndTag(HtmlTextWriterTag.Select)
        .EndTag(HtmlTextWriterTag.Div);
}

このスニペットが出力する HTML を「解読」できることを願っています。少なくともそれが目的です。

構文をどのように改善できるか、おそらくより良いメソッド名、おそらく他のアプローチについて何か考えを教えてください。

編集:流暢なインターフェースを使用しない場合、同じスニペットがどのように見えるかを比較するのは興味深いかもしれないと思いました:

public void RenderUsingHtmlTextWriterStandardMethods(HtmlTextWriter writer)
{
    writer.AddAttribute(HtmlTextWriterAttribute.Class, "someClass someOtherClass");
    writer.RenderBeginTag(HtmlTextWriterTag.Div);

    writer.RenderBeginTag(HtmlTextWriterTag.H1);
    writer.Write("Lorem");
    writer.RenderEndTag();

    writer.AddAttribute(HtmlTextWriterAttribute.Id, "fooSelect");
    writer.AddAttribute(HtmlTextWriterAttribute.Name, "fooSelect");
    writer.AddAttribute(HtmlTextWriterAttribute.Class, "selectClass");
    writer.RenderBeginTag(HtmlTextWriterTag.Select);

    writer.AddAttribute(HtmlTextWriterAttribute.Value, "1");
    writer.AddAttribute(HtmlTextWriterAttribute.Title, "Selects the number 1.");
    writer.RenderBeginTag(HtmlTextWriterTag.Option);
    writer.Write("1");
    writer.RenderEndTag();

    writer.AddAttribute(HtmlTextWriterAttribute.Value, "2");
    writer.AddAttribute(HtmlTextWriterAttribute.Title, "Selects the number 2.");
    writer.RenderBeginTag(HtmlTextWriterTag.Option);
    writer.Write("2");
    writer.RenderEndTag();

    writer.AddAttribute(HtmlTextWriterAttribute.Value, "3");
    writer.AddAttribute(HtmlTextWriterAttribute.Title, "Selects the number 3.");
    writer.RenderBeginTag(HtmlTextWriterTag.Option);
    writer.Write("3");
    writer.RenderEndTag();

    writer.RenderEndTag();

    writer.RenderEndTag();
}

編集:これの目標の1つは、オーバーヘッドをできるだけ少なくすることであるという点で、おそらくもう少し明確にする必要があります。これが、ラムダの使用を制限した理由です。また、最初はタグを表すクラスを使っていたので、レンダリング前の構文で DOM ツリーのようなものを構築していましたが、構文は非常に似ていました。この解決策は、わずかなメモリ オーバーヘッドが発生するため、断念しました。HtmlAttributeManager クラスの使用にはまだいくつかのこの存在があり、属性の追加にも拡張メソッドを使用することを考えていましたが、インデクサー構文を使用できず、HtmlTextWriter のインターフェイスを肥大化させます。さらに。

4

4 に答える 4

3

この種の構文を使用できるようにしたかったのです。

using (var w = new HtmlTextWriter(sw))
        {
            w.Html()
                .Head()
                    .Script()
                        .Attributes(new { type = "text/javascript", src = "somescript.cs" })
                        .WriteContent("var foo='bar'")
                    .EndTag()
                .EndTag()
                .Body()
                    .P()
                        .WriteContent("some content")
                    .EndTag()
                .EndTag()
            .EndTag();
        }

これを実現するために、HtmlTextWriter に拡張メソッドを追加しましたが、おそらくコンテナーの方が適切でしょう (まず、それを機能させることにもっと興味がありました!)使用可能なタグのそれぞれに対応するため、System.Web.UI.HtmlTextWriterTag 列挙型を反復処理することで、t4 テンプレートを使用してメソッドをコード生成します。タグ属性は匿名オブジェクトを使用して管理されます。コードは基本的に匿名型を反映し、プロパティを取り出して属性に変換します。これにより、結果の構文が非常にきれいな外観になると思います。

codegend の結果:

using System;
using System.Web.UI;
using System.Collections.Generic;


/// <summary>
///  Extensions for HtmlTextWriter
/// </summary>
public static partial class HtmlWriterTextTagExtensions
{
    static Stack<Tag> tags = new Stack<Tag>();



        /// <summary>
        ///  Opens a Unknown Html tag
        /// </summary>
        public static HtmlTextWriter Unknown(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("Unknown",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a A Html tag
        /// </summary>
        public static HtmlTextWriter A(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("a",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Acronym Html tag
        /// </summary>
        public static HtmlTextWriter Acronym(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("acronym",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Address Html tag
        /// </summary>
        public static HtmlTextWriter Address(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("address",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Area Html tag
        /// </summary>
        public static HtmlTextWriter Area(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("area",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a B Html tag
        /// </summary>
        public static HtmlTextWriter B(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("b",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Base Html tag
        /// </summary>
        public static HtmlTextWriter Base(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("base",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Basefont Html tag
        /// </summary>
        public static HtmlTextWriter Basefont(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("basefont",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Bdo Html tag
        /// </summary>
        public static HtmlTextWriter Bdo(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("bdo",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Bgsound Html tag
        /// </summary>
        public static HtmlTextWriter Bgsound(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("bgsound",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Big Html tag
        /// </summary>
        public static HtmlTextWriter Big(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("big",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Blockquote Html tag
        /// </summary>
        public static HtmlTextWriter Blockquote(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("blockquote",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Body Html tag
        /// </summary>
        public static HtmlTextWriter Body(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("body",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Br Html tag
        /// </summary>
        public static HtmlTextWriter Br(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("br",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Button Html tag
        /// </summary>
        public static HtmlTextWriter Button(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("button",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Caption Html tag
        /// </summary>
        public static HtmlTextWriter Caption(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("caption",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Center Html tag
        /// </summary>
        public static HtmlTextWriter Center(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("center",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Cite Html tag
        /// </summary>
        public static HtmlTextWriter Cite(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("cite",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Code Html tag
        /// </summary>
        public static HtmlTextWriter Code(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("code",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Col Html tag
        /// </summary>
        public static HtmlTextWriter Col(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("col",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Colgroup Html tag
        /// </summary>
        public static HtmlTextWriter Colgroup(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("colgroup",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Dd Html tag
        /// </summary>
        public static HtmlTextWriter Dd(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("dd",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Del Html tag
        /// </summary>
        public static HtmlTextWriter Del(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("del",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Dfn Html tag
        /// </summary>
        public static HtmlTextWriter Dfn(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("dfn",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Dir Html tag
        /// </summary>
        public static HtmlTextWriter Dir(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("dir",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Div Html tag
        /// </summary>
        public static HtmlTextWriter Div(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("div",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Dl Html tag
        /// </summary>
        public static HtmlTextWriter Dl(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("dl",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Dt Html tag
        /// </summary>
        public static HtmlTextWriter Dt(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("dt",  null));
            return writer;
        }

        /// <summary>
        ///  Opens a Em Html tag
        /// </summary>
        public static HtmlTextWriter Em(this HtmlTextWriter writer)
        {
            WritePreceeding(writer);
            tags.Push(new Tag("em",  null));
            return writer;
        }
于 2010-04-22T17:18:03.707 に答える
3

私が見る2つの問題があります:

  • の繰り返し使用Tag(Tagname, …)。タグ名ごとに拡張メソッドを提供しないのはなぜですか? 確かに、これはインターフェイスを肥大化させ、書くのはかなり大変です (=> コー​​ド生成!)。
  • コンパイラ/IDE は役に立ちません。特に、インデントをチェックしません (自動的にインデントすると破棄されます)。

どちらの問題も、Lambda アプローチを使用することで解決できる可能性があります。

writer.Write(body => new Tag[] {
    new Tag(h1 => "Hello, world!"),
    new Tag(p => "Indeed. What a lovely day.", new Attr[] {
        new Attr("style", "color: red")
    })
});

これは基本的なアプローチの 1 つにすぎません。API には確かにさらに多くの作業が必要です。特に、同じタグ名をネストすると、引数名が競合するため機能しません。また、このインターフェイスは VB ではうまく機能しません (またはまったく機能しません)。しかし残念なことに、Microsoft の PLINQ インターフェイスでさえ、他の最新の .NET API にも同じことが当てはまります。

少し前に私が考えた別のアプローチは、sambo のコードのように、実際に Markaby をエミュレートしようとします。主な違いは、usingの代わりにブロックforeachを使用しているため、RAII を利用していることです。

using (var body = writer.body("xml:lang", "en")) {
    using (var h1 = body.h1())
        h1.AddText("Hello, World!");
    using (var p = body.p("style", "color: red"))
        p.AddText("Indeed. What a lovely day.");
}

このコードには、他のアプローチの問題はありません。一方で、属性の型安全性が低く、インターフェイスが洗練されていません (「<em>elegant」の特定の定義に対して)。

両方のコードをコンパイルして、多かれ少なかれ意味のある出力 (つまり、HTML!) を生成することさえできます。

于 2009-01-05T22:13:28.173 に答える
1

この種のことをたくさん行う必要がある場合、NHaml のようなテンプレート エンジンを検討したことはありますか?

Ruby/Markaby では、これは非常にきれいに見えます。

    div :class=>"someClass someOtherClass" do 
        h1 "Lorem"
        select :id => "fooSelect", :name => "fooSelect", :class => "selectClass" do 
           option :title=>"selects the number 1", :value => 1 { "1" } 
           option :title=>"selects the number 2", :value => 2 { "2" } 
           option :title=>"selects the number 3", :value => 3 { "3" } 
        end
    end

同様のアプローチを.Netに移植できます

    using(var d = HtmlTextWriter.Div.Class("hello"))
    {
        d.H1.InnerText("Lorem"); 
        using(var s = d.Select.Id("fooSelect").Name("fooSelect").Class("fooClass"))
        {
           s.Option.Title("select the number 1").Value("1").InnerText("1"); 
        }
    } 

私はそれがかなり意志を読み取り、ネスティングをサポートしていると思います。

EDIT私はKonradから使用を盗んだので、読みやすくなりました。

元の提案には次の問題があります

  1. EndTag を呼び出すことを忘れないでください。そうしないと、HTML が Foobar になります。
  2. 名前空間が汚染されすぎています HtmlTextWriterTag が何度も繰り返され、オーバーヘッドからコンテンツを解読するのが困難です。

私が提案するアプローチは、潜在的にわずかに効率が悪いですが、これらの問題に対処し、非常に使いやすいと思います。

于 2009-01-05T22:16:52.130 に答える
0

これは、次の考慮事項に注意して、私が思いついたものです。

  1. T.Tagafterで入力を節約using T = HtmlTextWriterTag;しますが、好きかどうかは別として
  2. 呼び出しチェーンのシーケンスに対して少なくともある程度の安全性を確保したかった (これDebug.Assertは簡潔にするためのものであり、意図は明確である必要があります)
  3. HtmlTextWriter の無数のメソッドをラップしたくありませんでした。

    using T = HtmlTextWriterTag;
    
    public class HtmlBuilder {
      public delegate void Statement(HtmlTextWriter htmlTextWriter);
    
      public HtmlBuilder(HtmlTextWriter htmlTextWriter) {
        this.writer = htmlTextWriter;
      }
      // Begin statement for tag; mandatory, 1st statement
      public HtmlBuilder B(Statement statement) {
        Debug.Assert(this.renderStatements.Count == 0);
        this.renderStatements.Add(statement);
        return this;
      }
      // Attribute statements for tag; optional, 2nd to nth statement
      public HtmlBuilder A(Statement statement) {
        Debug.Assert(this.renderStatements.Count > 0);
        this.renderStatements.Insert(this.cntBeforeStatements++, statement);
        return this;
      }
      // End statement for tag; mandatory, last statement
      // no return value, fluent block should stop here
      public void E() {
        Debug.Assert(this.renderStatements.Count > 0);
        this.renderStatements.Add(i => { i.RenderEndTag(); });
        foreach (Statement renderStatement in this.renderStatements) {
            renderStatement(this.writer);
        }
        this.renderStatements.Clear(); this.cntBeforeStatements = 0;
      }
      private int cntBeforeStatements = 0;
      private readonly List<Statement> renderStatements = new List<Statement>();
      private readonly HtmlTextWriter writer;
    }
    
    public class HtmlWriter {
      public delegate void BlockWithHtmlTextWriter(HtmlTextWriter htmlTextWriter);
      public delegate void BlockWithHtmlBuilder(HtmlBuilder htmlBuilder);
    
      public string Render(BlockWithHtmlTextWriter block) {
        StringBuilder stringBuilder              = new StringBuilder();
        using (StringWriter stringWriter         = new StringWriter(stringBuilder)) {
            using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter)) {
                block(htmlTextWriter);
            }
        }
        return stringBuilder.ToString();
      }
      public string Render(BlockWithHtmlBuilder block) {
        return this.Render((HtmlTextWriter htmlTextWriter) => 
                block(new HtmlBuilder(htmlTextWriter)));
      }
      // small test/sample
      static void Main(string[] args) {
        HtmlWriter htmlWriter = new HtmlWriter();
        System.Console.WriteLine(htmlWriter.Render((HtmlBuilder b) => {
                b.B(h => h.RenderBeginTag(T.Div) )
                 .A(h => h.AddAttribute("foo", "bar") )
                 .A(h => h.AddAttribute("doh", "baz") )
                 .E();
            }));
      }
    }
    
于 2009-10-23T12:26:30.487 に答える