Custom HTML Helpers

Speed up the generation of common HTML blocks

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 MVC framework is that it enables developers to gain total control over generation of the HTML markup. In Web Forms, typically you use server controls to generate the markup. A server control, though, is like a black box of code, and although you can influence the format of HTML through properties, you can't control and style every single element. Server controls speed up page development but they sometimes represent a significant hurdle when you face strict requirements for XHTML support and HTML validation.

Among other things, ASP.NET MVC offers the mechanism of views to define the content of displayed pages in a way that closely resembles old-fashioned Active Server Pages. In a view, you find HTML literals along with blocks of managed code. Code blocks specify any part of the markup that is not constant and that depends on run time conditions and calculations.

HTML helpers, which can be valuable productivity tools, are public methods invoked from code blocks that return readymade chunks of HTML decorated with any data and attributes you provide as an argument. The ASP.NET MVC framework supplies a few HTML helpers out of the box, including CheckBox, DropDownList, and ActionLink. The stock set of HTML helpers, however, is insufficient for many real-world applications because it covers only the markup of basic HTML elements. In this regard, HTML helpers are significantly different from server controls because they completely lack abstraction over HTML.

Thankfully, the whole mechanism of HTML helpers is fully extensible and allows you to create your own helpers to generate just the piece of HTML you need. In this article, I'll walk through the steps required to create a custom HTML helper.

The HtmlHelper Class

In ASP.NET MVC, any HTML helper is an extension method defined over the system provided System.Web.Mvc.HtmlHelper class. In Figure 1, you can see the Visual Studio 2008 IntelliSense window and the icon that identifies an extension method.

Figure 1: HTML helpers are extension methods on HtmlHelper

An instance of HtmlHelper is associated with the ViewPage and ViewUserControl classes for developers to invoke any registered helpers in their views. The property that exposes the HTML helper object is named Html. As a 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 ViewPage or 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. Figure 2 lists some of the class's public members that authors of HTML helpers may find 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 extension method defined on the HtmlHelper class. Subsequently, an HTML helper's source code 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 an HTML helper that generates a full-fledged check box element with an associated label. The extension method takes the name of the resulting element, a Boolean value that indicates the requested initial state, and the desired text for the label.

The process of generating the HTML markup is entirely under your control, and you can use any approach you feel comfortable with. Here's a very simple, yet effective, approach:

string format = "

 

";

response = String.Format(format,

(isChecked ?"checked" :""),

isChecked,

name,

label);

The expected HTML template is expressed using a format string where quotes have been omitted for simplicity. Your own extensions would go to a separate class library and require an @Import directive in the target view.

<%@ Import Namespace="MyHtml" %>

If you compare the signature of the sample CheckBox helper shown here with the signature of other built-in helpers, you'll see that a few method overloads are missing. All built-in helpers, therefore, supply an overload that accepts an object or a dictionary filled with HTML attributes. In addition, you may have noticed that the built-in CheckBox helper also emits a hidden input field along with the check box element. The two input elements share the same name. In this way, when the browser posts the content of a form there 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 is natively implemented to make up for a strange behavior of browsers that do not transmit anything about a check box in a form that is unchecked.)

Let's see how to improve our CheckBox helper to have it emit the same markup as the standard helper plus the associated label.

Pseudo-Inheritance for HTML Helpers

Extension methods have nothing to do with class inheritance. Extension methods are simply a way to add a new method to an existing class; the compiler has the burden of joining new and existing methods in the resulting class v-table at runtime. To reuse the capabilities of the existing CheckBox helper, simply write a set of extension methods that wrap the code 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);

}

#endregion

private static string AddLabel(string response, string name, string label)

{

// Add one blank

StringBuilder builder = new StringBuilder(response);

builder.Append(" ");

// Add the label

builder.AppendFormat("", name, label);

return builder.ToString();

}

}

Figure 3: A custom CheckBox HTML helper

In 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 MVC view and the resulting page.

Figure 4: A LabeledCheckBox helper in action

The TagBuilder Helper Class

When building a completely custom HTML helper, and not simply extending a well-known existing helper, you might want to rely on some specialized builder instead of handcrafting all the HTML yourself. The ASP.NET MVC framework makes available the TagBuilder class a utility that just makes it easier to generate HTML strings programmatically. Overall, the behavior of the TagBuilder class is not much different from the popular StringBuilder class you find in the .NET Framework. Most of the built-in extension methods just use the TagBuilder class internally. The code snippet below shows how to use the TagBuilder class to create a piece of markup that represents 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 an attribute. 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, you must 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 the System.Web.Routing assembly that you must reference from the project.

To build a combination of an HTML CheckBox, a hidden field, and a label, you need to invoke the TagBuilder repeatedly for each tag and combine the results using a classic StringBuilder object. The complete 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 dictionary

IDictionary dict;

dict = ((IDictionary) new RouteValueDictionary(htmlAttributes));

// Build the checkbox

TagBuilder 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 builder

StringBuilder builder = new StringBuilder();

builder.Append(checkbox.ToString(TagRenderMode.SelfClosing));

// Build the hidden field

TagBuilder 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 blank

builder.Append(" ");

// Build the label

TagBuilder lbl = new TagBuilder("label");

lbl.MergeAttribute("for", name);

// Build the text within the label element

TagBuilder 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 text of 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 property instead.

Providing a Helping Hand

ASP.NET MVC revolutionizes ASP.NET development by implementing a web-specific variation of the popular pattern Model-View-Controller. Every request is mapped to a method on the controller class, and every action originates a new view that typically results in HTML markup sent to the browser. The content of the view is processed by the view engine. The default view engine is based on a subset of the ASP.NET Web Forms rendering engine. It recognizes master pages and server controls and also allows for using plain HTML literals interspersed with chunks of context-specific data and markup.

HTML helpers are the native tool provided to speed up context-specific and data-driven HTML development. The default view engine of ASP.NET MVC lacks a true component model; HTML helpers are a sort of replacement but cover only a limited number of scenarios. Whether you opt for using HTML helpers or write markup directly is a matter of preference. For sure, helpers just help and to some extent improve your productivity. However, I wouldn't say that helpers are really a productivity booster and using plain markup is definitely an option.

Writing custom HTML helpers is easy, and it gives you a great chance to create your own made-to-measure toolset. A number of free and open-source HTML helpers, however, are contributed by the community via the codeplex.com/mvccontrib website. They are definitely worth a look.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish