Rendering HTML with the HtmlTextWriter isn't incredibly intuitive in my opinion, but if you're implementing web controls in web forms it's what you have to work with. I thought that it might be possible to create a fluent interface for this that reads a bit more like the HTML it outputs. I would like to know what people think of the syntax that I've come up with so far.
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" and "EndTag" are extension methods for the HtmlTextWriter class that returns the instance it takes in so that calls can be chained. The argument passed to the lambda used in the overload used by the first call to "Tag" is a "HtmlAttributeManager", which is simple class that wraps an HtmlTextWriter to provide an indexer that takes an HtmlTextWriterAttribute and a string value and returns the instance so that calls can be chained. I also have methods on this class for the most common attributes, such as "Name", "Class" and "Id" so that you could write the first call above as follows:
.Tag(HtmlTextWriterTag.Div, e => e.Id("id").Name("name").Class("class"))
A little longer example:
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);
}
Hopefully you'll be able to "decipher" what HTML this snippet outputs, at least that's the idea.
Please give me any thoughts on how the syntax can be improved upon, maybe better method names, maybe some other approach all together.
Edit: I thought it might be interesting to see what the same snippet would look like without the use of the fluent interface, for comparison:
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: I should probably be a little more explicit in that one of the goals with this is that it should incur as little overhead as possible, this is why I've limited the use of lambdas. Also at first I used a class that represented a tag so that something similar to a DOM-tree was built by the syntax before the rendering, the syntax was very similar though. I abandoned this solution for the slight memory overhead it incurs. There are still some of this present in the use of the HtmlAttributeManager class, I have been thinking about using extension methods for the appending of attributes also, but the I can't use the indexer-syntax, also it bloats the interface of the HtmlTextWriter even more.