Conventional HTML in ASP.NET MVC: Baseline behavior

Other posts in this series:

Now that we’ve got the pieces in place for building input/display/label conventions, it’s time to circle back and figure out what exactly we want our default behaviors to be for each of these components. Because it’s so easy to modify the tags generated programmatically, we can establish some pretty decent site-wide behavior for our system.

First, in order to establish a baseline, we need to examine what our current implicit standards are. Right now I’m solely focused on only the input/label/display elements, and not how these elements are typically composed together (label + input etc.). Looking at several of our inputs, we see a prevalent pattern. All of the input elements (ALL of them) have a CSS class appended to them for Bootstrap, “form-control”. Appending this in our normal method of the MVC templated helpers is actually quite difficult and esoteric. For us, it’s a snap.

First, let’s create our own HtmlConventions class that inherits from the default:

public class OverrideHtmlConventions 
    : DefaultHtmlConventions {
}

We’ll then redirect our container configuration to use this convention library instead:

public class FubuRegistry : Registry
{
    public FubuRegistry()
    {
        var htmlConventionLibrary 
             = new HtmlConventionLibrary();
        var conventions
             = new OverrideHtmlConventions();
        htmlConventionLibrary.Import(.Library);
        For<HtmlConventionLibrary>().Use(htmlConventionLibrary);

The OverrideHtmlConventions class is where we’ll apply our own conventions on top of the existing ones. The base conventions class lets us apply conventions to several classes of items:

  • Displays
  • Editors
  • Labels

And a couple of things I won’t cover as I’ve never used them:

  • Forms
  • Templates

There’s no real difference between the Displays/Editors/Templates conventions – it’s just a way to segregate strategies and conventions for building different kinds of HTML elements.

Conventions work by pairing a filter and a behavior. The filter is “whom to apply” and the behavior is “what to do”. You have many different levels of applying filters:

  • Always (global)
  • Attribute existence (w/ filtering on value)
  • Property metadata

The last one is interesting – you have the entire member metadata to work with. You can look at the property name, the property type and so on.

From there, your behaviors can be as simple or complex as you need. You can:

  • Add/remove CSS classes
  • Add/remove HTML attributes
  • Add/remove data attributes
  • Replace the entire HTML tag with a new, ground-up version
  • Modify the HTML tag and its children

You have a lot of information to work with, the original value, new value, containing model and more. It’s pretty crazy, and a lot easier to work with than the MVC metadata (which goes through this ModelMetadata abstraction).

I want to set up our “Always” conventions first, which means really only adding CSS classes. The input elements are easy:

Editors.Always.AddClass("form-control");

Our input elements become a bit simpler now:

@*Before*@
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@*After*@
@Html.Input(m => m.Email)

Our labels are a bit more interesting. Looking across the app, it appears that all labels have two CSS classes applied, one pertaining to styling a label, and one pertaining to width. At this point we need to make a judgment call. Do we standardize that all labels are a certain width? Or do we force all of our views to explicitly set this class?

Luckily, we can still adopt a site-wide convention and replace this CSS class as necessary. Personally, I’d rather standardize on how screens should look rather than each new screen becoming a point of discussion on how wide/narrow things are. Standardize, but allow deviation. Our label configuration now becomes:

Editors.Always.AddClass("form-control");
Labels.Always.AddClass("control-label");
Labels.Always.AddClass("col-md-2");

Then in our HTML, we can replace our labels with our convention-based version:

<div class="form-group">
    @Html.Label(m => m.Email)
    <div class="col-md-10">
        @Html.Input(m => m.Email)
    </div>
</div>
<div class="form-group">
    @Html.Label(m => m.Password)
    <div class="col-md-10">
        @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
    </div>
</div>
<div class="form-group">
    @Html.Label(m => m.ConfirmPassword)
    <div class="col-md-10">
        @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
    </div>
</div>

It turns out in this app so far we’re not using display elements, but we could go a similar path (surrounding the element with a span tag etc).

So what about those other methods, the “PasswordFor” and so on? In the next article, we’ll look at replacing all of the form helpers with our version, based solely on metadata that already exists on our view models.

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.