09/11/2011

ASP.NET MVC - Extending HtmlHelper

Today, we will be looking at extending the HtmlHelper for ASP.NET MVC in C# using .NET 3.5sp1/4.

Lets look at some introductory basics:
Syntax for creating Extension methods
public static class {extensionclassname}
{
  public static {returntype} {methodName}(this {objectToExtend} {name})
  {
  }
}
So lets say we want to create an input helper, we could do something like this
public static class HtmlHelperExtensions
{
  public static InputHelper<T> Input<t>(this HtmlHelper<T> htmlHelper)
  {
    return new InputHelper(htmlHelper);
  }
}

public class InputHelper<T>
{
  private readonly HtmlHelper<T> _htmlHelper;

  public InputHelper(HtmlHelper<T> htmlHelper)
  {
    _htmlHelper = htmlHelper;
  }
}

Although it's not necessary to create a separate class to hold the methods for the input helper, it is preferred because of separation of concerns (SoC).

Next, we would add the namespace wherein the HtmlHelperExtensions is located to the <namespaces> section under pages < system.web.webPages.razor in the Web.Config located in the Views directory.

To access our input helper, we would call @Html.Input() and from there on custom methods to render a MvcHtmlString (which is a string - already safe for use as razor output - a normal string would be html encoded).

Here is an interesting use case:
Lets say we want to wrap html in a <div class="input"><- html here -></div> without having to manually write out <div class="input">....</div> every time?

This is done pretty easily, lets add a method to our existing InputHelper<T> class:
public MvcHtmlString Wrap(Func<object, HelperResult> htmlToWrap)
{
  var htmlBuilder = new StringBuilder();

  htmlBuilder.Append("<div class='input'>");

  htmlBuilder.Append(htmlToWrap.Invoke(_htmlHelper));

  htmlBuilder.Append("</div>");

  return MvcHtmlString.Create(htmlBuilder.ToString());
}

Here is an example on how to use this method from within a view:
@Html.Input().Wrap(@<text><label for="name" />@Html.TextBox("name")</text>)

The @<text>...</text> being passed to the method can be seen as a view which gets 'compiled' to html. So instead of writing to ouput:
<label for="name" />@Html.TextBox("name")
  using  htmlToWrap.Invoke(_htmlHelper) compiles down to
<label for="name" /><input type="text" id="name" name="name" />

HtmlHelper extensions are miracilious in the sense they can give you extreme refratoring capabilities and can do much more than one expects.

For example, I have written input extensions that simply calls
@Html.Input().TextBoxFor(model => model.MobileNo)
which in turn adds a label and applies mask behavior and validation logic to the element (behavior and validation via unobtrusive data-* attributes that javascript applies).

All in one line of code - and you could do the same!

No comments:

Post a Comment