2009-01-05 12 views
10

representación HTML con el HtmlTextWriter no es muy intuitivo en mi opinión, pero si va a implementar controles web en formularios web que es lo que tiene que trabajar. Pensé que sería posible crear una interfaz fluida para esto que se parece un poco más al HTML que genera. Me gustaría saber qué opina la gente sobre la sintaxis que he presentado hasta ahora.interfaz fluida para la representación 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();   
    } 

"Etiqueta", "texto" y "etiqueta de cierre" son los métodos de extensión para la clase HtmlTextWriter que devuelve la instancia en que se necesita para que las llamadas se pueden encadenar. El argumento que se pasa a la lambda utilizado en la sobrecarga utilizado por la primera llamada a la "etiqueta" es un "HtmlAttributeManager", que es la clase simple que envuelve una HtmlTextWriter para proporcionar un indexador que toma un HtmlTextWriterAttribute y un valor de cadena y devuelve la instancia para que las llamadas se pueden encadenar También tengo métodos en esta clase para los atributos más comunes, tales como "Nombre", "clase" y "ID" de manera que se podría escribir la primera llamada por encima de la siguiente manera:

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

Un pequeño ejemplo más:

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

Esperamos que pueda ser capaz de "descifrar" lo HTML salidas de este fragmento, al menos esa es la idea.

Por favor, dame alguna idea de cómo la sintaxis puede ser mejorado, tal vez mejores nombres de métodos, tal vez algún otro enfoque todos juntos.

Editar: pensé que podría ser interesante ver lo que el mismo fragmento vería sin el uso de la interfaz fluida, para la comparación:

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

EDIT: probablemente debería ser un poco más explícito en que uno de los objetivos con esto es que debe incurrir en la menor sobrecarga posible, esta es la razón por la que he limitado el uso de lambdas. También al principio usé una clase que representaba una etiqueta para que la sintaxis construyera algo similar a un árbol DOM antes de la representación, aunque la sintaxis era muy similar. Abandoné esta solución por la leve sobrecarga de memoria en la que incurre. Todavía hay algo de esta presente en el uso de la clase HtmlAttributeManager, he estado pensando acerca del uso de los métodos de extensión para la Anexión de atributos también, pero el que no puedo usar el indexador-sintaxis, también se hincha la interfaz de la HtmlTextWriter aún más.

+0

Esto es muy bueno. Quiero preguntarte algo antes de responder ... ¿por qué estás usando la sintaxis del indexador? No veo un beneficio para él, y claramente es intrusivo en su sintaxis. Creo que probablemente tengas una buena razón para eso que estoy pasando por alto. –

+0

Era lo mejor que se me ocurría, si no lo usaba, la sintaxis sería algo así como "t => t.Attribute (clave, valor) .Atributo (clave, valor)" que crece bastante. ¿Tiene otra idea de cómo hacer esto? Las sugerencias son más que bienvenidas. –

+0

¿Hubo alguna razón por la que no pudo hacer esto: .Tag (HtmlTextWriterTag.Select) .Attribute ("Value", "1"). Attribute ("Title", "Selecciona el número 1"). EndTag() –

Respuesta

3

Hay dos cuestiones que veo:

  • El uso repetido de Tag(Tagname, …). ¿Por qué no ofrecer métodos de extensión para cada nombre de etiqueta? Es cierto que esto abota la interfaz y es mucho para escribir (=> generación de código).
  • El compilador/IDE no lo ayuda. En particular, no comprueba la sangría (incluso la destruirá cuando sangres automáticamente).

Ambos problemas quizá podrían ser resueltos mediante el uso de un enfoque 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") 
    }) 
}); 

Esta es sólo una aproximación básica. La API ciertamente necesitaría mucho más trabajo. En particular, anidar el mismo nombre de etiqueta no funcionará debido a conflictos de nombre de argumento. Además, esta interfaz no funcionaría bien (o no funcionaría) con VB. Pero luego, desafortunadamente, lo mismo ocurre con otras API .NET modernas, incluso con la interfaz PLINQ de Microsoft.

Otro enfoque que he pensado hace un tiempo realmente intenta emular a Markaby, como el código de sambo. La diferencia principal es que estoy usando using bloques en lugar de foreach, aprovechando así 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."); 
} 

Este código no tiene los problemas del otro enfoque. Por otro lado, proporciona menos seguridad de tipo para los atributos y una interfaz menos elegante (para una definición dada de "elegant").

Obtengo ambos códigos para compilar e incluso producir algunos resultados más o menos significativos (es decir: ¡HTML!).

+0

En realidad tengo métodos de extensión para los nombres de etiqueta más comunes, así que creo que es una gran idea. He jugado con el enfoque lambda, pero no se puede anidar es un factor decisivo, que no juega con VB es en realidad otro, ya que trabajo en algunas aplicaciones grandes escritas en VB. –

+0

En realidad, acabo de notar que mi idea tiene varios problemas serios que la rompen en la práctica. Actualmente estoy tratando de solucionar algunos de ellos y actualizaré mi respuesta en consecuencia a su debido tiempo. –

+0

He actualizado el código y he agregado otra proposición. –

1

Si necesita hacer muchas cosas de este tipo, ¿ha considerado alguna clase de motor de plantillas como NHaml?

En Ruby/Markaby esto se vería mucho más bonito.

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 

Puede puerto de un enfoque similar a .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"); 
     } 
    } 

Creo que lee bastante voluntad y apoya anidación.

EDIT Robé el uso de Konrad porque se lee mucho mejor.

Tengo el siguiente problema con la propuesta original

  1. Usted debe recordar llamar etiqueta de cierre de lo contrario su HTML va Foobar.
  2. Su namspace está muy contaminado HtmlTextWriterTag se repite una tonelada de veces y es difícil descifrar el contenido de la sobrecarga.

Mi enfoque sugerido es potencialmente un poco menos eficiente, pero creo que aborda estas preocupaciones y sería muy fácil de usar.

+0

Sí, tienes razón, se ve mucho más bonito. La interfaz en la que estoy trabajando es para usar en situaciones donde no tienes otra opción. –

+0

Acepto que el código en su segundo ejemplo se lee bien, sin embargo, ¿cree que se lee mejor que mi enfoque? Esto también introduce una ligera sobrecarga (memoria y tiempo para la construcción de los objetos) que quiero evitar. Tal vez no debería ser tan cuidadoso con esa ligera sobrecarga. –

+0

@Patrik Expandí mi respuesta ... –

0

Esto es lo que me ocurrió, el cuidado de las siguientes consideraciones:

  1. ahorro otra tipificación con T.Tag después using T = HtmlTextWriterTag;, cuales te pueden gustar o no
  2. quería obtener, al menos, algo de seguridad para la secuencia de la cadena de invocación (el Debug.Assert es solo por brevedad, la intención debe ser clara)
  3. No quería envolver la miríada de métodos del 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

que quería ser capaz de tener este tipo de sintaxis:

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

Con el fin de lograr esto he añadido los métodos de extensión a la HtmlTextWriter aunque un recipiente probablemente sería más apropiado (¡estaba más interesado en hacerlo funcionar antes que nada!) Sintiéndome perezoso, no quería escribir un método para cada una de las etiquetas disponibles, así que codifiqué los métodos usando una plantilla t4 iterando a través de System.Web.UI.HtmlTextWriterTag enum. Los atributos de etiqueta se administran usando objetos anónimos; el código básicamente se refleja en el tipo anónimo, extrae las propiedades y las convierte en atributos que creo que le da a la sintaxis resultante una apariencia muy limpia.

El resultado 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; 
     } 
+0

+1 por funkiness –

+0

¿Puedes poner esto en GitHub? Sería un proyecto genial –

+0

Lo publiqué en codeplex: http://fluenthtmlwriter.codeplex.com/ –

Cuestiones relacionadas