Custom HTML Helpers
Speed up the generation of common HTML blocks
October 30, 2009
RELATED: "HTML5 Is in Style: Working with CSS3 and HTML5" and "HTML5 for the ASP.NET Developer"
One of the best selling points of the ASP.NET MVCframework is that it enables developers to gain total control over generationof the HTML markup. In Web Forms, typically you use server controls to generatethe markup. A server control, though, is like a black box of code, and althoughyou can influence the format of HTML through properties, you can't control andstyle every single element. Server controls speed up page development but they sometimesrepresent a significant hurdle when you face strict requirements for XHTMLsupport and HTML validation.
Among other things, ASP.NET MVC offers themechanism of views to define the content of displayed pages in a way thatclosely resembles old-fashioned Active Server Pages. In a view, you find HTMLliterals along with blocks of managed code. Code blocks specify any part of themarkup that is not constant and that depends on run time conditions andcalculations.
HTML helpers, which can be valuable productivity tools,are public methods invoked from code blocks that return readymade chunks ofHTML decorated with any data and attributes you provide as an argument. TheASP.NET MVC framework supplies a few HTML helpers out of the box, includingCheckBox, DropDownList, and ActionLink. The stock set of HTML helpers, however,is insufficient for many real-world applications because it covers only themarkup of basic HTML elements. In this regard, HTML helpers are significantly differentfrom server controls because they completely lack abstraction over HTML.
Thankfully, the whole mechanism of HTML helpers isfully extensible and allows you to create your own helpers to generate just thepiece of HTML you need. In this article, I'll walk through the steps requiredto create a custom HTML helper.
The HtmlHelper Class
In ASP.NET MVC, any HTML helper is an extensionmethod defined over the system provided System.Web.Mvc.HtmlHelper class. In Figure1, you can see the Visual Studio 2008 IntelliSense window and the icon that identifiesan extension method.
Figure 1: HTML helpers are extension methods on HtmlHelper
An instance of HtmlHelper is associated with the ViewPage andViewUserControl classes for developers to invoke any registered helpers intheir views. The property that exposes the HTML helper object is named Html. Asa result, you can write the following code in MVC views and have it work:
<% = this.Html.CheckBox("CheckBox1", true) %>
Typically, such code belongs to a class derived from ViewPageor ViewUserControl. The code belongs to ViewPage if you're writing a page view;it belongs to ViewUserControl if you're writing a user control view. Overall,the HtmlHelper class represents support for rendering HTML controls in a view. Figure2 lists some of the class's public members that authors of HTML helpers mayfind helpful.
Member | Description |
---|---|
ViewContext | Property that encapsulates any information related to rendering an ASP.NET MVC view, including the View object and the ViewData container. |
ViewData | Property that represents the container used for passing data between a controller and a view. |
AntiForgeryToken | Method that renders out a piece of HTML useful to fight off Cross-Site Request Forgery (CSRF) attacks. Note that just emitting CSRF markup is not enough. In ASP.NET MVC, you also need to decorate the controller's method that processes the form with the CSRF markup with the ValidateAntiForgeryToken attribute. |
AttributeEncode | Method that converts the value of a given attribute to a string and then encodes it to neutralize potentially harmful HTML characters. |
Encode | Method that just converts to string and then HTML-encodes the specified value. |
GenerateLink | Static helper method that returns a link for the specified route values. |
GetFormMethodString | Static helper method that returns the form method string (GET or POST). |
GetInputTypeString | Static helper method that returns the input type string (radio, checkbox, submit, and so forth) |
Figure 2: The public interface of the HtmlHelper class
Creating a Custom HTML Helper
As mentioned, any HTML helper is an extensionmethod defined on the HtmlHelper class. Subsequently, an HTML helper's sourcecode will always follow the pattern below:
namespace MyHtml{public static class CheckBoxHelper{public static string LabeledCheckBox(this HtmlHelper helper,string name, bool isChecked, string label){string response = String.Empty;// HTML generation:return response;}}}
The preceding code snippet shows the template for anHTML helper that generates a full-fledged check box element with an associatedlabel. The extension method takes the name of the resulting element, a Booleanvalue that indicates the requested initial state, and the desired text for thelabel.
The process of generating the HTML markup is entirelyunder your control, and you can use any approach you feel comfortable with.Here's a very simple, yet effective, approach:
string format = " {3}";response = String.Format(format,(isChecked ?"checked" :""),isChecked,name,label);
The expected HTML template is expressed using a format stringwhere quotes have been omitted for simplicity. Your own extensions would go toa separate class library and require an @Import directive in the target view.
<%@ Import Namespace="MyHtml" %>
If you compare the signature of the sample CheckBoxhelper shown here with the signature of other built-in helpers, you'll see thata few method overloads are missing. All built-in helpers, therefore, supply anoverload that accepts an object or a dictionary filled with HTML attributes. Inaddition, you may have noticed that the built-in CheckBox helper also emits ahidden input field along with the check box element. The two input elementsshare the same name. In this way, when the browser posts the content of a formthere will be an explicit reference to an input element with the given name,regardless of whether the check box is checked or unchecked. (This trick isnatively implemented to make up for a strange behavior of browsers that do nottransmit anything about a check box in a form that is unchecked.)
Let's see how to improve our CheckBox helper to haveit emit the same markup as the standard helper plus the associated label.
Pseudo-Inheritance for HTML Helpers
Extension methods have nothing to do withclass inheritance. Extension methods are simply a way to add a new method to anexisting class; the compiler has the burden of joining new and existing methodsin the resulting class v-table at runtime. To reuse the capabilities of theexisting CheckBox helper, simply write a set of extension methods that wrap thecode to extend. Figure 3 shows an example.
public static class CheckBoxHelper{public static string LabeledCheckBox(this HtmlHelper helper,string name, string label){string response = helper.CheckBox(name);return CheckBoxHelper.AddLabel(response, name, label);}public static string LabeledCheckBox(this HtmlHelper helper,string name, bool isChecked, string label){string response = helper.CheckBox(name, isChecked);return CheckBoxHelper.AddLabel(response, name, label);}public static string LabeledCheckBox(this HtmlHelper helper,string name, IDictionary htmlAttributes,string label){string response = helper.CheckBox(name, htmlAttributes);return CheckBoxHelper.AddLabel(response, name, label);}public static string LabeledCheckBox(this HtmlHelper helper,string name, object htmlAttributes, string label){string response = helper.CheckBox(name, htmlAttributes);return CheckBoxHelper.AddLabel(response, name, label);}public static string LabeledCheckBox(this HtmlHelper helper,string name, bool isChecked,IDictionary htmlAttributes,string label){string response = helper.CheckBox(name, isChecked, htmlAttributes);return CheckBoxHelper.AddLabel(response, name, label);}public static string LabeledCheckBox(this HtmlHelper helper,string name, bool isChecked, object htmlAttributes,string label){string response = helper.CheckBox(name, isChecked, htmlAttributes);return CheckBoxHelper.AddLabel(response, name, label);}#endregionprivate static string AddLabel(string response, string name, string label){// Add one blankStringBuilder builder = new StringBuilder(response);builder.Append(" ");// Add the labelbuilder.AppendFormat("{1}", name, label);return builder.ToString();}}Figure 3: A custom CheckBox HTML helperIn Figure 3, the LabeledCheckBox HTML helper first invokes the built-in CheckBox method as defined to extend the HtmlHelper class and captures its output. Next, it calls an internal method to merge that markup with a label HTML element.public static string LabeledCheckBox(this HtmlHelper helper,string name, string label){string response = helper.CheckBox(name);return CheckBoxHelper.AddLabel(response, name, label);}
Figure 4 shows how the new helper is used in an ASP.NET MVCview and the resulting page.
Figure 4: A LabeledCheckBox helper in action
The TagBuilder Helper Class
When building a completely custom HTML helper, andnot simply extending a well-known existing helper, you might want to rely onsome specialized builder instead of handcrafting all the HTML yourself. TheASP.NET MVC framework makes available the TagBuilder class a utility that justmakes it easier to generate HTML strings programmatically. Overall, thebehavior of the TagBuilder class is not much different from the popularStringBuilder class you find in the .NET Framework. Most of the built-inextension methods just use the TagBuilder class internally. The code snippetbelow shows how to use the TagBuilder class to create a piece of markup thatrepresents a plain CheckBox.
TagBuilder checkbox = new TagBuilder("input");checkbox.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.CheckBox));checkbox.MergeAttribute("name", name, true);if (isChecked){checkbox.MergeAttribute("checked", "checked");}
You use the MergeAttribute method to specify the value of anattribute. If you hold a dictionary that contains a bunch of HTML attributes,you can also use another overload of the MergeAttribute method.
span.MergeAttributes(htmlAttributes);
Finally, if HTML attributes are passed via a .NET object, youmust first load the content into an ad hoc dictionary, as shown below:
IDictionary dict;dict = ((IDictionary) new RouteValueDictionary(htmlAttributes));span.MergeAttributes(dict);
The RouteValueDictionary object is defined in theSystem.Web.Routing assembly that you must reference from the project.
To build a combination of an HTML CheckBox, ahidden field, and a label, you need to invoke the TagBuilder repeatedly foreach tag and combine the results using a classic StringBuilder object. Thecomplete code is shown in Figure 5.
public static string LabeledCheckBox(this HtmlHelper helper,string name, bool isChecked, object htmlAttributes, string label){// Convert from object to dictionaryIDictionary dict;dict = ((IDictionary) new RouteValueDictionary(htmlAttributes));// Build the checkboxTagBuilder checkbox = new TagBuilder("input");checkbox.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.CheckBox));checkbox.MergeAttribute("name", name, true);if (isChecked){checkbox.MergeAttribute("checked", "checked");}// Initialize the string builderStringBuilder builder = new StringBuilder();builder.Append(checkbox.ToString(TagRenderMode.SelfClosing));// Build the hidden fieldTagBuilder hidden = new TagBuilder("input");hidden.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));hidden.MergeAttribute("name", name);hidden.MergeAttribute("value", "false");builder.Append(hidden.ToString(TagRenderMode.SelfClosing));// Add a blankbuilder.Append(" ");// Build the labelTagBuilder lbl = new TagBuilder("label");lbl.MergeAttribute("for", name);// Build the text within the label elementTagBuilder span = new TagBuilder("span");span.MergeAttributes(dict);span.SetInnerText(label);lbl.InnerHtml = span.ToString();builder.Append(lbl.ToString());return builder.ToString();}
Figure 5: Using the TagBuilder object
The TagBuilder class allows you to set the inner textof the element via the SetInnerText method. The text is automatically HTML encoded.If you want to set the inner text as HTML, you use the InnerHTML propertyinstead.
Providing a Helping Hand
ASP.NET MVC revolutionizes ASP.NET development byimplementing a web-specific variation of the popular patternModel-View-Controller. Every request is mapped to a method on the controllerclass, and every action originates a new view that typically results in HTMLmarkup sent to the browser. The content of the view is processed by the viewengine. The default view engine is based on a subset of the ASP.NET Web Formsrendering engine. It recognizes master pages and server controls and alsoallows for using plain HTML literals interspersed with chunks ofcontext-specific data and markup.
HTML helpers are the native tool provided to speedup context-specific and data-driven HTML development. The default view engineof ASP.NET MVC lacks a true component model; HTML helpers are a sort ofreplacement but cover only a limited number of scenarios. Whether you opt forusing HTML helpers or write markup directly is a matter of preference. Forsure, helpers just help and to some extent improve your productivity. However,I wouldn't say that helpers are really a productivity booster and using plainmarkup is definitely an option.
Writing custom HTML helpers is easy, and it gives youa great chance to create your own made-to-measure toolset. A number of free andopen-source HTML helpers, however, are contributed by the community via thecodeplex.com/mvccontrib website. They are definitely worth a look.
About the Author
You May Also Like