2009-01-05 43 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; 
     }