Conventional HTML in ASP.NET MVC: Building larger primitives

Other posts in this series:

We’ve taken our individual form elements quite far now, adopting a variety of conventions in our output to remove the boring, duplicated code around deciding how to display those individual elements. We have everything we need to extend and alter the conventions around those single elements.

And that’s a good thing – we want to keep using those primitives around individual elements. But what if we peeked a little bit larger? Are there larger common patterns in play that we can start incorporating? Let’s look at the basic input template, from Bootstrap:

<div class="form-group">
    @Html.Label(m => m.Password)
    <div class="col-md-10">
        @Html.Input(m => m.Password)
        @Html.ValidationMessageFor(m => m.Password, ""
          , new { @class = "text-danger" })
    </div>
</div>

Starting with the most basic element, the input tag, let’s look at gradually increasing scope of our building block:

  • Input
  • Input and validation
  • Input, validation in a div
  • Label and the div input
  • Form group

The tricky part here is that at each level, I want to be able to affect the resulting tags, some or all. Our goal here is to create building blocks for each level, so that we can establish a set of reusable components with sensible defaults along the way. This is a similar exercise as building a React class or Angular directive – establish patterns early and standardize your approach.

From the above list of items, I’ll likely only want to create blocks around increasing scopes of DOM elements, so let’s whittle this down to 3 elements:

  • Input
  • Input Block
  • Form Block

We already have our original Input method, let’s create the first input block.

Basic input block

Because we have our HtmlTag primitive, it’s trivial to combine elements together. This is a lot easier than working with strings or MvcHtmlStrings or the less powerful TagBuilder primitive. We’ll return the outer div tag, but we still need ways of altering the inner tags. This includes the Label, the Input, and Validator. Here’s our input block:

public static HtmlTag InputBlock<T>(this HtmlHelper<T> helper,
    Expression<Func<T, object>> expression,
    Action<HtmlTag> inputModifier = null,
    Action<HtmlTag> validatorModifier = null) where T : class
{
    inputModifier = inputModifier ?? (_ => { });
    validatorModifier = validatorModifier ?? (_ => { });

    var divTag = new HtmlTag("div");
    divTag.AddClass("col-md-10");

    var inputTag = helper.Input(expression);
    inputModifier(inputTag);

    var validatorTag = helper.Validator(expression);
    validatorModifier(validatorTag);

    divTag.Append(inputTag);
    divTag.Append(validatorTag);

    return divTag;
}

We create an Action<HtmlTag> for the input/validator tags. If someone wants to modify those two elements directly, instead of wonky anonymous-objects-as-dictionaries, we allow them full access to the tag via a callback, similar to jQuery. Next, we default those two modifiers to no-op if they are not supplied.

We then build up our input block, which consists of the outer div with the input tag and validator tag as children. In our view, we can replace the input block:

<div class="form-group">
    @Html.Label(m => m.Email)
    @Html.InputBlock(m => m.Email)
</div>
<div class="form-group">
    @Html.Label(m => m.Password)
    <div class="col-md-10">
        @Html.Input(m => m.Password)
        @Html.Validator(m => m.Password)
    </div>
</div>

Just to contrast, I included the non-input-blocked version. Now that we have this piece, let’s look at building the largest primitive, the form block.

Form input block

In the same tradition of Angular directives, React classes and Ember views, we can build larger components out of smaller ones, reusing the smaller components as necessary. This also ensures our larger component automatically picks up changes from the smaller ones. Here’s our FormBlock method:

public static HtmlTag FormBlock<T>(this HtmlHelper<T> helper,
    Expression<Func<T, object>> expression,
    Action<HtmlTag> labelModifier = null,
    Action<HtmlTag> inputBlockModifier = null,
    Action<HtmlTag> inputModifier = null,
    Action<HtmlTag> validatorModifier = null
    ) where T : class
{
    labelModifier = labelModifier ?? (_ => { });
    inputBlockModifier = inputBlockModifier ?? (_ => { });

    var divTag = new HtmlTag("div");
    divTag.AddClass("form-group");

    var labelTag = helper.Label(expression);
    labelModifier(labelTag);

    var inputBlockTag = helper.InputBlock(
        expression, 
        inputModifier, 
        validatorModifier);
    inputBlockModifier(inputBlockTag);

    divTag.Append(labelTag);
    divTag.Append(inputBlockTag);

    return divTag;
}

It’s very similar to our input block method, where we provide defaults for our initializers, create the outer div tag, build the child tags, apply child modifiers, and append those child tags to the outer div. Going back to our view, it becomes quite simplified:

@Html.FormBlock(m => m.Email)
@Html.FormBlock(m => m.Password)
<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <div class="checkbox">
            @Html.Input(m => m.RememberMe).RemoveClasses()
            @Html.Label(m => m.RememberMe).RemoveClasses()
        </div>
    </div>
</div>

We have one outlier, our “remember me” checkbox, which I try to avoid at all costs. Let’s look at a couple of other examples. Here’s our register view:

@Html.ValidationSummary("", new { @class = "text-danger" })
@Html.FormBlock(m => m.Email)
@Html.FormBlock(m => m.Password)
@Html.FormBlock(m => m.ConfirmPassword)
<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <input type="submit" class="btn btn-default" value="Register" />
    </div>
</div>

And here’s our reset password view:

@Html.ValidationSummary("", new { @class = "text-danger" })
@Html.Input(m => m.Code)
@Html.FormBlock(m => m.Email)
@Html.FormBlock(m => m.Password)
@Html.FormBlock(m => m.ConfirmPassword)
<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <input type="submit" class="btn btn-default" value="Reset" />
    </div>
</div>

Much more simplified, with conventions around the individual input elements and HtmlHelper extensions around larger blocks. I would likely go an additional step and create an HtmlHelper extension around the buttons as well, since Bootstrap buttons have a very predictable, standardized set of HTML to build out.

We’ve removed a lot of our boilerplate HTML, but still allowed customization as needed. We also still expose the smaller components through InputBlock and Input, so that if the HTML varies a lot we can still keep the smaller building blocks. There’s still a bit of magic going on, but it’s only a click away to see what “FormBlock” actually does. Finally, what conventions really allow us to do is stop focusing on the minutiae of the HTML we have to include on every display/input block of HTML.

We remove bike shedding arguments and standardize our approach, allowing us to truly hone in on what is interesting, challenging or different. This is the true power of conventions – stop pointless and wasteful arguments about things that truly don’t matter through a standardized, but extensible, approach built on conventions.

In our next (and final) post, we’ll look at how we can extend these approaches for client-side templates built in Angular, Ember and more.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • carbonatethis

    If only the business could agree that we should standardize. Too often, the final say rests with individuals who want every page to look and feel differently, regardless of what is best practice or more maintainable.

    • jbogard

      That can be addressed in a few ways:
      - Exposing cost of designs for those features
      - Providing a style tile/style guide for looks of individual elements outside of context
      - Only providing wireframes, NOT comps/photoshop screens for approvals
      All about how it’s presented, in my experience.

  • katokay

    I wonder if react, specifically when using jsx handles the same goal without the magic and with better traceability?

    • jbogard

      That’s a good question, and to be honest, I haven’t built enough with React to know. The next post on client-side templates might help though with your answer?

  • gilligan_MH

    what about templating component viewmodels? Like if I have na Address View model and I want to display it a certain way?

    • jbogard

      I’d still use partials for that I think.

    • Harry McIntyre

      Try feeding your html from the component into a CsQuery CQ object, then manipulating it using JQuery-style syntax (e.g. cq["input"].AddClass(“blah”))

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1660

  • http://www.a10online.com/ a10

    I am impressed by the details that you have here. It reveals how nicely you perceive this subject. Thanks

  • Ciel

    At the risk of being annoying, when the next (and purportedly final) post to this series is made, can you have it include links to all of the previous ones in the right order, and in their entirety?
    One thing I’m frustrated with is that it seems almost random which articles in the series are linked on these. The only way I’ve found to get them all is to scroll down to your “about” section and ask to see all posts made by you, and to view them there.

  • David Keaveny

    When creating more complex controls, what’s the general guidance for selecting your base tag? For instance, I’m creating a helper for a Bootstrap-styled datepicker control, which consists of an outer div, a textbox and a button. I have been creating a new tag (public class DatepickerWidget : HtmlTag { public DatepickerWidget() : base(“div”) {} } } and then appending the textbox and button; the trouble is that any use of the HtmlTag DSL then gets added to the div rather than the textbox. If I remove the outer div from the DatepickerWidget and in my view use .WrapWith(new DivTag()) then that works as I would expect it; but I would rather not have to remember in each reference in the view to add the .WrapWith call; adding the .WrapWith call at the end of the DatepickerWidget constructor doesn’t work either – I can see the div being set as the Parent of the control, but it never gets rendered,