DRYing MVC Forms with an EditorEntryFor Helper

When creating forms in ASP.NET MVC I use a small helper to keep the code DRY (Don’t Repeat Yourself). The EditorEntryFor helper creates everything needed for a form field – the label, the input and the validation.

When creating line of business applications, a huge part of the coding is often to create forms. Each form can consist of a huge number of fields and each field requires some common formatting. It’s usually quite straightforward with a few divs surrounding a label and the input field. Running the default MVC scaffolding tooling, that’s exactly what’s generated:

<div class="editor-label">
    @Html.LabelFor(model => model.TopSpeed)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.TopSpeed)
    @Html.ValidationMessageFor(model => model.TopSpeed)
</div>

In my opinion that code has two severe problems:

  • It’s too verbose.
  • It’s repeating itself for each form field, making the class name and div structure a pain to modify.

In my projects I usually create a small helper, so the above lines of code can be replaced with one single statement:

@Html.EditorEntryFor(model => model.TopSpeed)

In fact, I can now create (nearly) the entire form with less lines of code than one single form field required before:

@Html.EditorEntryFor(model => model.RegistrationNumber)
@Html.EditorEntryFor(model => model.TopSpeed)
@Html.EditorEntryFor(model => model.Color)
@Html.EditorEntryFor(model => model.Seats)

With so simple code to build up forms, I find that I never use the scaffolding. Typing no longer takes that much time, and I want to have control over the order of the fields and how they are grouped in fieldsets anyways.

The EditorEntryFor Helper Method

The helper method is actually made up of two methods. The first creates the markup for the different sections of the form entry. The second one adds the surrounding html and combines everything into one large string. The reason for them to be separate is a separation of concerns. The knowledge of what parts should be included and how to combine them are two separate concerns.

/// <summary>
/// Helper methods for form creation
/// </summary>
public static class FormExtensions
{
  /// <summary>
  /// Creates an entry in a form, complete with label, input and validation message.
  /// </summary>
  /// <typeparam name="TModel">Type of the model.</typeparam>
  /// <typeparam name="TValue">Type of the field.</typeparam>
  /// <param name="html">Html helper.</param>
  /// <param name="expression">An expression that identifies the field of the model to render 
  /// an editor entry for</param>
  /// <returns>MvchHtmlString</returns>
  public static MvcHtmlString EditorEntryFor<TModel, TValue>(
    this HtmlHelper<TModel> html,
    Expression<Func<TModel, TValue>> expression)
  {
    return BuildFormEntry(html.LabelFor(expression), 
      html.EditorFor(expression), html.ValidationMessageFor(expression));
  }
 
  private static MvcHtmlString BuildFormEntry(
    MvcHtmlString label, MvcHtmlString input, MvcHtmlString validation)
  {
    return new MvcHtmlString("<div class=\"editor-label\">" + label + "</div>\n" +
    "<div class=\"editor-field\">" + input + validation + "</div>\n\n");
  }
}

To make the helper accessible from the views, the namespace (In my case MvcApplication.Utils) need to be imported in the web.config in the views folder.

<namespaces>
  <add namespace="System.Web.Mvc" />
  <add namespace="System.Web.Mvc.Ajax" />
  <add namespace="System.Web.Mvc.Html" />
  <add namespace="System.Web.Routing" />
  <add namespace="MvcApplication.Models"/>
  <add namespace="MvcApplication.Utils"/>
</namespaces>

For form intensive applications this helper can really save a lot of typing and also enables layouts to be changed much easier than with the code created by the scaffolding. I’ve used this approach in a couple of major projects myself and it just gets stronger over time, when the look, feel and behaviour of forms can be consistently changed throughout the entire system.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.