Work with ASP.NET MVC Templated HTML Helpers
Unleash the power of ASP.NET MVC templated helpers to support dates and enumerated types in input forms
August 24, 2011
It's fairly common for web views to include input forms; and it's fairly common for those input forms to require a very specific layout that accomplishes two things: merges the forms with the surrounding graphics and adds rich client-side goodies. These input forms should probably be written from scratch without relying on any layout facilities. However, in many situations (more than you might think at first), the process of building the layout of input forms can be automated to save time and annoyance.
You can, for instance, put automation to work in writing the back-office system of a website. Such a system typically contains forms for editing records. Because these pages are not accessible to the general public, customers usually don't care much about the graphics in them.
This is just the kind of situation in which ASP.NET MVC templated helpers shine. Templated helpers are, basically, HTML helpers. That is, they serve as plain HTML factories that ASP.NET MVC applications can use to speed up the authoring of HTML views. Templated helpers can take an object (even the whole model as passed to the view) and build a read-only or editable HTML form out of that. More likely, though, you use templated helpers to display or edit individual properties of an object. In this article, I'll show you how to use templated helpers in a few not-so-common scenarios, such as in forms that use dates and enumerated types.
Modeling Input Forms
There are two groups of templated helpers: Display and Editor. There are several variations of each kind, such as EditorFor and EditorForModel. Under the hood, all that these helpers really do is to figure out which viewer or editor template best fits the provided data. Templated helpers include a lot of predefined templates and the conventions for applying them to given data. You can override these templates and conventions by using ad hoc attributes.
Which attributes would you apply? And where would you apply them?
Here's the scenario for an example form: Suppose you want users to specify a pivot date value and a time value that represents the quantity of days, weeks, or minutes to add or subtract. Figure 1 shows a possible user interface for this form.
Figure 1: Sample input form containing dates, number, and enumerations
As you can see, the example form includes three distinct fields for day, month, and year, an additional text box for the time quantity, and a drop-down list of time quantity labels (days, minutes, and weeks). Two submit buttons complete the form, one to add the specified quantity and one to subtract it.
This is plain HTML stuff, and no special skills are required to make such a form work as expected. But what exactly do you expect this form to return? How would you ideally express your expectations?
You can use the following class to represent any data that gets entered in the Figure 1 form:
public class DateEditorViewModel
{
public DateTime? PivotDate { get; set; }
public DateMeasurements AvailableMeasurements { get; set; }
public Int32 Quantity { get; set; }
public String Output { get; set; }
}
In this class, the PivotDate member indicates the basic date, the Quantity member indicates the number of ticks to add, and the Output member is a return message for the user. DateMeasurements is an enumerated type that lists which quantities you can add or subtract, as follows:
public enum DateMeasurements
{
Days = 0x1,
Months = 0x2,
Years = 0x4,
Minutes = 0x8,
Seconds = 0x10,
Weeks = 0x20
}
To display the form initially, use the following controller method:
public ActionResult Index()
{
var model = new DateEditorViewModel();
return View(model);
}
To receive data that's posted from the form, use the following method:
public ActionResult Calc(DateEditorViewModel inputModel)
{
:
}
The model binding infrastructure of ASP.NET MVC ensures that posted values (or values that are available through route or query string parameters) are copied to members of the input model type. This is true as long as a match is found between posted parameters and the names of public properties. The markup in Figure 2 renders the form. Actually, if you run this code as-is, you'll get a different kind of output. Specifically, you get no drop-down list for enumerated values, and you get a single text box for the date. To achieve the desired results as shown in Figure 1, you have to attach a bit more metadata to the view model class.
MetadataType and the UIHint Attribute
By adding the MetadataType attribute to a class, as shown in Figure 3, you add meta information to the class members without spoiling the original source code.
In Figure 3, the Quantity member is declared as required and is restricted to accepting integers greater than 0. The UIHint attribute indicates the custom template that the helper should use in lieu of the standard one. ASP.NET MVC defines templates for most common property types, including strings, Booleans, nullable Booleans, integers, and dates. These standard templates generate a validation error message if invalid data is entered. This functionality is enabled by the interaction between templated helpers, data annotation attributes, native metadata, and the validation providers in ASP.NET MVC.
The string you pass to UIHint refers to a partial view (i.e., a user control, if you're using the ASPX view engine) that's located in the EditorTemplates folder under either Views/{controller} or Views/Shared. The custom template receives a model object and renders the expected user interface. Figure 4 shows a custom Razor template for a date that's displayed in three text boxes.
The Razor view descends from WebViewPage, where T is just the type you display through the template—in this case, a DateTime value. Note that you can also opt for a less complex approach that drops the @inherits keyword for a simpler @model keyword. In this case, though, you cannot access metadata information that's exposed through ViewData. In particular, in Figure 4, the use of GetFullHtmlFieldName lets you create markup that resembles the following for each input field:
name="PivotDate.Day"
id="PivotDate.Day"
class="easydate_editor_dd"
value="7" />
The name and id attributes are composed of the name of the view model property to which they refer and the specific piece of information that they represent. This naming scheme guarantees that you'll have no conflicts if you place multiple date elements in the same page. Note also that the dot in the name/id attributes may give you a hard time if, for instance, you use the jQuery library to attach a plug-in or an event handler to the text boxes. The following code shows how to make such an attachment of a custom plugin that edits the day segment of a date. Note the double backslash (\) sequence escaping the dot symbol in a CSS query string that's processed by jQuery.
$("#PivotDate\.Day").dateEditor(DateEditor.Day).css("background-color", "#d0f5a9");
Without escaping the dot, the wrapped set remains empty.
In Figure 2, you can see a text box that's used to edit the value of the Quantity property. You can't see it in the figure, but the Quantity text box results from a custom template. You might want to use a custom editor template for a plain integer if you don't want to see validation messages that are displayed close to the text box. Here's the custom template I use when I want to edit a value and not create validation messages:
@inherits System.Web.Mvc.WebViewPage
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
new { @class = "easydate_editor_quantity" })
It's important to note that a logical DateTime value that's split into three text boxes will post three different values, and these values must be recombined into a single value. The model-binding system can do this, but it requires that you provide a custom binder component that can return a DateTime value. In other words, creating a logical gap between the data in the model and its graphical representation requires a specific model binder component to bridge the gap. Using a custom template sometimes forces you to use a custom model binder, too.
Editing Enumerated Types
ASP.NET MVC doesn't provide an ad hoc template for members that belong to an enumerated type. Both the display and editor templates consist of a plain string. In editing mode, an enumerated value is treated the same as descriptive text and is edited through an unfiltered text box. A very simple way to improve the value is to write a custom template for the string type. This process entails writing a partial view named string.cshtml (or string.ascx) in the ViewsSharedEditorTemplates folder, as Figure 5 shows.
The code shown in Figure 5 is very basic yet fully functional. The code checks whether the model type descends from System.Enum and, if so, you make it editable through a drop-down list that shows all possible values for the enumerated type.
By using a custom template such as the one in Figure 5, you create a drop-down list that shows options that match the name of the enumerated value. For example, the list shows Weeks, Months, and Days in the case of the aforementioned DateMeasurements type. You are limited to a single word (perhaps CamelCase) and, more importantly, this text is not localizable at all.
A better solution involves using a couple of extension methods for the System.Enum type, as shown in Figure 6. The GetItems extension method works as a smart replacement for native Enum.GetValues method. It extends the native Enum.GetValues method in two aspects: It supports the Description attribute on enumerated values, and it supports bitwise selection. Suppose that you rewrite the enum type, as follows:
public enum DateMeasurements
{
[Description("day(s)"]
Days = 0x1,
:
}
The Description attribute is available in the System.ComponentModel namespace. The GetItems method returns a collection of EnumItem objects for which the Text property is set to the value of the Description attribute or, alternatively, to the name of the value. The EnumItem class is a simple item class that has two properties: Description and Value. You can rewrite the template for enumerated types as follows:
@model Object
@if (Model is Enum)
{
var m = Model as Enum;
@Html.DropDownList("",
new SelectList(m.GetItems(), "Value", "Description"),
new { @class = "dropdown-list" })
}
:
The Description attribute accepts a string, but the string can't be an expression that requires runtime execution. In other words, you can't set the attribute by using a string expression that changes according to the current culture. You can derive a new attribute from Description that supports localization, as Figure 7 shows. The following code snippet shows how to use the new LocalizableDescription attribute:
public enum DateMeasurements
{
[LocalizableDescription("day(s)", ResourceName="Days", ResourceType="typeof(Strings)"]
Days = 0x1,
:
}
The signature of the attribute is nearly identical to the signature of other data annotations attributes that you may be familiar with.
Bitwise Selection of Enumerated Values
The GetItems method also recognizes enumerated types that are decorated with the Flags attribute. On an enumerated type, the Flag attribute enables you to support bitwise values and, more importantly, instructs the ToString method to return a comma-separated string that combines the values, as follows:
[Flags]
public enum DateMeasurements
{
:
}
In light of this, the GetItems method can display only the values that are combined in a bitwise operation. Here's an example:
var model = new DateEditorViewModel();
model.AvailableMeasurements = DateMeasurements.Weeks |
DateMeasurements.Days |
DateMeasurements.Minutes;
return View(model);
As you can see in Figure 8, the list shows only the three elements from this example.
Figure 8: Selection of the enumeration
Keeping It Simple
Templated helpers and data annotations work well together; there's no question. However, a templated helper works best in simple scenarios, such as demos and tutorials. When you move the tool to the real world, it keeps working but forces you to make compromises more often than you may expect. So either you give up on using custom templates or you introduce custom binders to your project. (Custom binders are a whole separate layer of functionality in ASP.NET MVC that deserves further investigation. If you never explored this area, I heartily encourage you to do so as soon as possible.)
But when you adapt them appropriately through custom templates, template helpers are a great feature because they reduce your view code to a single line. In addition, they help to distribute any complexity in your code through a variety of relatively simple templates.
Dino Esposito ([email protected]) is CTO of e-tennis.net, specialists in web and mobile solutions for professional sports. He is also the author of Programming Microsoft ASP.NET MVC (Microsoft Press) and a trainer and consultant specializing in web architectures and effective code design principles and practices. Follow Dino on Twitter: @despos.
About the Author
You May Also Like