Sometimes I’m a nitpicker. Actually I’m a nitpicker 24/7, but I can’t really say that on a blog, can I?
So today’s nitpicking is about C#’s TagBuilder. This class allows you to build html without writing a single ‘<’ or ‘>’. Using this class usually boils down to something like this:
// this will create <button>
var input = new TagBuilder("button");
// Set some attributes
input.Attributes.Add("name", "property.name");
input.Attributes.Add("id", "property_id");
// some classes for markup
input.AddCssClass("btn btn-primary");
// equivalent to jQuery's $("property_id").text("Click me!");
input.InnerHtml = "Click me!";
However, wouldn’t it be nicer if we could say:
// this will create <button>
var input = new TagBuilder("button")
// Set some attributes
.Attribute("name", "property.name")
.Attribute("id", "property_id")
// some classes for markup
.Class("btn btn-primary")
// equivalent to jQuery's $("property_id").text("Click me!");
.Html("Click me!");
It’s not a huge difference, but all the actions are chainable, provide a nice and consistent API and follow the Builder pattern. Hey, if you’re going to call your class the TagBUILDER it’s the least you can do. As hard as I tried, I couldn’t restrain myself and did the unthinkable: I wrote extension methods. As it turns out, it’s far from a hurdle to implement this kind of thing:
/// <summary>
/// Helper methods that allow builder-style construction of tags
/// </summary>
public static class ExtensionsForTagBuilder
{
#region Attribute
/// <summary>
/// Sets an attribute on this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="attribute">The attribute to set</param>
/// <param name="value">The value to set</param>
/// <param name="replaceExisting">A value indicating whether the value should override the existing value should there be one.</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Attribute(this TagBuilder @this, string attribute, string value, bool replaceExisting = false)
{
@this.MergeAttribute(attribute, value, replaceExisting);
return @this;
}
/// <summary>
/// Sets an attribute on this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="attribute"></param>
/// <param name="html"></param>
/// <param name="replaceExisting">A value indicating whether the value should override the existing value should there be one.</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Attribute(this TagBuilder @this, string attribute, IHtmlString html, bool replaceExisting = false)
{
return Attribute(@this, attribute, html.ToString(), replaceExisting);
}
#endregion
#region Class
/// <summary>
/// Adds a class to this tag.
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="class">The class(es) to add</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Class(this TagBuilder @this, string @class)
{
@this.AddCssClass(@class);
return @this;
}
#endregion
#region Html
/// <summary>
/// Sets the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html">The html to set as the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Html(this TagBuilder @this, string html)
{
@this.InnerHtml = html;
return @this;
}
/// <summary>
/// Sets the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="tag">The tag to set as the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Html(this TagBuilder @this, TagBuilder tag)
{
return Html(@this, tag.ToString());
}
/// <summary>
/// Sets the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html">The html to set as the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder Html(this TagBuilder @this, IHtmlString html)
{
return Html(@this, html.ToString());
}
#endregion
#region AppendHtml
/// <summary>
/// Appends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html"></param>
/// <returns>This tagbuilder</returns>
public static TagBuilder AppendHtml(this TagBuilder @this, string html)
{
if (@this.InnerHtml == null)
@this.InnerHtml = string.Empty;
@this.InnerHtml += html;
return @this;
}
/// <summary>
/// Appends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="tag">The tag to append to the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder AppendHtml(this TagBuilder @this, TagBuilder tag)
{
return AppendHtml(@this, tag.ToString());
}
/// <summary>
/// Appends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html">The html to append to the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder AppendHtml(this TagBuilder @this, IHtmlString html)
{
return AppendHtml(@this, html.ToString());
}
#endregion
#region PrependHtml
/// <summary>
/// Prepends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html">The html to prepend to the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder PrependHtml(this TagBuilder @this, string html)
{
if (@this.InnerHtml == null)
@this.InnerHtml = string.Empty;
@this.InnerHtml = html + @this.InnerHtml;
return @this;
}
/// <summary>
/// Prepends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="tag">The tag to prepend to the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder PrependHtml(this TagBuilder @this, TagBuilder tag)
{
return PrependHtml(@this, tag.ToString());
}
/// <summary>
/// Prepends to the InnerHtml property of this tag
/// </summary>
/// <param name="this">This tagbuilder</param>
/// <param name="html">The html to prepend to the html for this tagbuilder</param>
/// <returns>This tagbuilder</returns>
public static TagBuilder PrependHtml(this TagBuilder @this, IHtmlString html)
{
return PrependHtml(@this, html.ToString());
}
#endregion
/// <summary>
/// Renders and returns the element as a <see cref="F:System.Web.Mvc.TagRenderMode.Normal"/> element.
/// </summary>
/// <param name="this">This tag</param>
/// <returns>The rendered element as a <see cref="F:System.Web.Mvc.TagRenderMode.Normal"/> element</returns>
public static MvcHtmlString ToHtml(this TagBuilder @this)
{
return MvcHtmlString.Create(@this.ToString());
}
/// <summary>
/// Renders and returns the HTML tag by using the specified render mode.
/// </summary>
/// <param name="this">This tag</param>
/// <param name="renderMode">The render mode.</param>
/// <returns>The rendered HTML tag by using the specified render mode</returns>
public static MvcHtmlString ToHtml(this TagBuilder @this, TagRenderMode renderMode)
{
return MvcHtmlString.Create(@this.ToString(renderMode));
}
}
This is one of the things I really love about C#: you can roll your own API over existing classes as if they were there in the first place. Using this builder pattern should make your HtmlHelpers much more readable. :-)
See you next time!