2009-01-05 44 views
10

用我的意見來渲染HTML與HtmlTextWriter並不是非常直觀,但是如果你正在web窗體中實現web控件,那麼你必須使用它。我認爲有可能爲此創建一個流暢的接口,它的讀取更像是它輸出的HTML。我想知道人們到底想到了什麼語法。流暢的用於渲染的界面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();   
    } 

「標籤」,「文本」和「結束標記」是返回它需要在這樣的呼叫可以鏈接實例HtmlTextWriter類的擴展方法。傳遞給用於第一次調用「標記」的重載的lambda的參數是一個「HtmlAttributeManager」,它是一個簡單的類,它封裝了一個HtmlTextWriter以提供一個索引器,它接受一個HtmlTextWriterAttribute和一個字符串值並返回實例該呼叫可以被鏈接。我也有這個類最常見的屬性的方法,如「名稱」,「類」和「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(); 
} 

編輯: 我也許應該多一點明確的一點是,它應該承擔儘可能少的開銷,這就是爲什麼我限制使用lambda表達式。此外,起初我使用了一個代表標籤的類,以便在渲染之前通過語法構建類似於DOM樹的東西,但語法非常相似。我放棄了這個解決方案,因爲它產生了輕微的內存開銷。在使用HtmlAttributeManager類時仍然存在一些這樣的情況,我一直在考慮使用擴展方法來附加屬性,但是我不能使用索引器語法,也擴展了HtmlTextWriter的接口更。

+0

這是很酷的。在我回復之前,我想問你一些問題...你爲什麼使用索引器語法?我沒有看到它的好處,顯然它侵入你的語法。我想你可能有一個很好的理由,我可以忽略它。 – 2009-03-29 06:45:23

+0

這是最好的我可以拿出來,如果我不使用它的語法會像「t => t.Attribute(key,value).Attribute(key,value)」那樣增長很大。對於如何做到這一點,你有另一個想法,建議是非常值得歡迎的。 – 2009-03-30 07:06:39

+0

是否有這樣的原因:.Tag(HtmlTextWriterTag.Select).Attribute(「Value」,「1」)。Attribute(「Title」,「Selects the number 1」)。EndTag() – 2009-04-01 05:11:56

回答

3

有兩個問題,我看到:

  • 重複使用的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") 
    }) 
}); 

這僅僅是一個基本途徑。 API當然需要更多的工作。特別是,由於參數名稱衝突,嵌套相同的標記名稱將不起作用。而且,這個接口在VB中不能很好地工作(或根本不能)。但是,對於其他現代.NET API,即使是來自Microsoft的PLINQ接口,情況也是如此。

我前段時間想過的另一種方法實際上是試圖模仿馬卡比,就像桑博的代碼一樣。主要的區別是,我使用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."); 
} 

此代碼沒有其他辦法的問題。另一方面,它爲屬性提供了較少的類型安全性,並提供了一個不太優雅的界面(對於「優雅」的給定定義)。

我得到這兩個代碼來編譯,甚至產生一些或多或少有意義的輸出(即:HTML!)。

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"); 
     } 
    } 

我認爲它會讀取相當的意志和支持嵌套。

編輯我偷了Konrad的使用,因爲它讀得好多了。

我與原提案

  1. 你必須記住,否則叫你結束標記HTML去Foobar的以下問題。
  2. 您的namspace太污染了HtmlTextWriterTag重複了很多次,而且很難從頭頂上解讀內容。

我建議的方法可能效率稍低,但我認爲它解決了這些問題,並且會很容易使用。

0

這是我想出了,採取以下考慮照顧:

  1. 我節省一些打字T.Tagusing 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(); 
         })); 
        } 
    } 
    
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; 
     }