Display Templates and Editor Templates for Fun and Profit*

If you've ever worked with Razor in ASP.NET MVC, I'm sure you've noticed a few built-in HtmlHelpers, Html.DisplayFor, Html.DisplayForModel, Html.EditorFor and Html.EditorForModel. The latter two methods basically just generate an HTML input based on the type of the passed in property, or in the case of Html.EditorForModel, an input for each property in the model, while the former two methods will render the value of a property or properties, in the case of Html.DisplayForModel. It's actually kind of magical the first time you try it, that is, until you want to alter the generated HTML in some way.

For my part, when I was first cutting my teeth on ASP.NET MVC, I very quickly outgrew these methods, mostly due to integrating the Twitter Bootstrap framework. Every form input had to have some special class or attribute, so I found myself switching to using Html.TextBoxFor and the like. However, by doing that, I lost a good bit of integration with things like data annotations on my model. For example, if a property is annotated with [Required], and you use Html.EditorFor, it will add the required validation attributes to your HTML input, but, if you use Html.TextBoxFor, those attributes are not added. Instead, you must specify a required attribute manually, i.e.:

@Html.TextBoxFor(m => m.SomeProperty, new { required = true })

This rubbed me the wrong way because I take DRY almost as a religion. Any time I have to specify something twice, that's twice as many chances for me to make a mistake, and Mr. Murphy says that I will take that opportunity to screw up. More over, I found myself doing the same patterns of inputs over and over. When I wanted rich-text editor, I would do something along the lines of:

 @Html.TextAreaFor(m => m.RichTextProperty, new { @class = "html" })

With the "html" class being a catch for my JavaScript rich-text editor to bind to. It's not that arduous, but it's still repetitious and of course, leads to that inevitable moment every single time, where you load the page, seeing just a regular textarea and realizing you forgot to add the class.

Enter (Display || Editor) Templates

I heard rumors of these things called "display templates" and "editor templates". They promised to solve all of my problems and more, perhaps even ending world hunger and securing peace on Earth. I toyed with them, but still being very green, I found them completely daunting. Documentation was sparse, and ultimately, I abandoned the effort and continued my torturous form building as before.

Then one day, I had had enough, and I suppose I finally reached a level of aptitude working with ASP.NET MVC that I felt confident enough to really dig in and make these templates work. It still took some trial and error, but I started to understand, and since I'm an all-around nice guy, I want to share that understanding with you in the hopes that you will also embrace the joys of display templates and editor templates.

So, what the heck are these things?

Simply, they're just views, partial views specifically, as they don't employ a layout. But, they're kind of magical views, in that based on naming conventions, Razor will load the right one up for the specific scenario. To get started, you simply create folders named "DisplayTemplates" and "EditorTemplates" in your Views\Shared project directory. Then, in each of these two folders, you add partial views, as needed, named after the type you want to represent, e.g. the view, Views\Shared\DisplayTemplates\String.cshtml, will be used any time you call Html.DisplayFor on a property of type string. Likewise, the view, Views\Shared\EditorTemplates\DateTime.cshtml, will be used anytime you call Html.EditorFor with a property of type DateTime.

But wait, there's more. There's also the DataType attribute in System.ComponentModel.DataAnnotations, which allows you to specify various data types on your properties. The members of the DataType enum (passed as a parameter to the DataType attribute) are also available as template names. For example, I can decorate a property with [DataType(DataType.EmailAddress)] and then create the views, Views\Shared\DisplayTemplates\EmailAddress.cshtml and/or Views\Shared\EditorTemplates\EmailAddress.cshtml, and those view will then be used to render any properties decorated with this data type.

Sounds simple enough. What's the catch?

Well, if you're anything like me, it wasn't figuring out which view name to create that was the problem, but what the heck should go into it that caused me fits. See, although you can specify a model for thse views just like any other, the model you'd use is typically a static type, something like String or DateTime, and these don't respond to things like Html.TextBoxFor because there's no properties on them. You could always just resort to Html.TextBox or a straight <input type="text"> but then there's the question of how do you know dynamically what name and id to give it? Well, it turns out, this part is a bit magical too.

Simply, you don't worry about it. "Huh?", you ever-so-eloquently respond. Well, whenever you need to pass a name to something like Html.TextBox, you just pass an empty string instead. Razor will helpfully replace the right value for you when rendering the view.

However, that just covers the field name. For other things, we have access to the object, ViewData.TemplateInfo. This object is filled with helpful methods to retrieve bits of information we're likely to need. For example, you can get the current value of the field via ViewData.TemplateInfo.FormattedModelValue. If you need the id of the field, you can use ViewData.TemplateInfo.GetFullHtmlFieldId(string partialFieldName). But wait, doesn't that require the name of the property? In case you haven't guessed it, yep, you can just pass an empty string.

Example

Let's say we want a email address field that will wrap a mail icon addon, in Bootstrap-style, and also use the appropriate HTML5 input type of "email", so supporting browsers will automatically validate our field. If we were to do this manually, it would look something like:

<div class="input-group">
    <span class="input-group-addon">
        <i class="glyphicon glyphicon-envelope"></i>
    </span>
    @Html.TextBoxFor(m => m.Email, new { type="email", @class="form-control" })
</div>

That's a fair bit of HTML to remember, and definitely not something you want to find yourself repeating over and over through your views. Now let's see how EditorTemplates can help us out a bit:

Views\Shared\EditorTemplates\EmailAddress.cshtml

<div class="input-group">
    <span class="input-group-addon">
        <i class="glyphicon glyphicon-envelope"></i>
    </span>
    @Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { type="email", @class="form-control" })
</div>

This is pretty similar to our manual code, but notice that we're now using an Html.TextBox control. The first parameter should be the name for the field. Since there's no way for us to know this dynamically, we just pass an empty string, as discussed earlier. The second parameter is the current value of the field, if any. For this, we use ViewData.TemplateInfo to retrieve the formatted model value. Finally, the third parameter is any HTML attributes we want on the generated input. We pass the same anonymous object we used before.

The beauty part about this though is that we only have to write this HTML once. Generating this for every email address field throughout our site is as easy as:

  1. Decorate your model property:

    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
    
  2. Use Html.EditorFor

    @Html.EditorFor(m => m.Email)
    

Don't you just love when things are simple? Now, if I decide I don't want that email icon wrapped around the field after all, I can change just one place in my code, EmailAddress.cshtml, and every email address field across my site is changed instantly.

Now, let's create a DisplayTemplate for our email address properties. When ever I show the value in list and detail views, I want the email address to be linked with a mailto, so I just create a new view:

Views\Shared\DisplayTemplates\EmailAddress.cshtml

@model String
@if (!string.IsNullOrWhiteSpace(Model))
{
    <a href="mailto:@Model">@Model</a>
}

Notice here that I went ahead and defined a model for this view. This isn't required, but it allowed my code to be more succinct as I'm dealing with a known type, String, instead of just an object. If you didn't specify a model for the view, you could still get at the data via ViewData.TemplateInfo.FormattedModelValue but you would need to cast that to a String anyways in order to check if it's null or empty (where having just whitespace is considered "empty"). That check, of course, is necessary, to ensure that we only create the link if we actually have something to link to.

Wrap-up

I hope you're already seeing how easy and powerful DisplayTemplates and EditorTemplates can be. I confess that in retrospect, it seems silly that I was so perplexed by these originally. However, I suspect that I'm also not alone, which is why I'm sharing this. In my next post, I'm going to dive deeper to show how you can cover some more complex scenarios.

* Profit not guaranteed. No warranties expressed or otherwise

comments powered by Disqus