Chapter 12. Models and Data Entry

Over the last few chapters, you've learned in some detail about how ASP.NET MVC lets you separate your HTML generation and application logic concerns using views and controllers. But most web applications ultimately revolve around data. Even if the user interface is very sophisticated and customized, it's typically all about letting users browse and edit information. In MVC architecture, we use the term models for the data objects being passed between controllers and views. These models can be sophisticated domain models that encapsulate business logic and are persisted as entities in a database, or they can be just simple view models that are never saved and merely hold a set of properties that a view can render.

ASP.NET MVC tries not to have strong opinions about how your models should work. Unlike Ruby on Rails, for example, ASP.NET MVC doesn't expect you to inherit model classes from a certain base class or use a particular database access technology. Instead, its goal is to leave you in control so you can use anything from the extensive .NET ecosystem.

But if the MVC Framework doesn't know anything about your models, how can it automatically handle any of the tedious work related to data entry and display? In this chapter, you'll learn how the framework uses conventions to cope with most situations simply by inspecting the property names and types on your models, and offers extensibility mechanisms to let you provide extra information or alternative conventions, in each of the following use cases:

  • Using templated view helpers to generate portions of user interface directly from your models and their properties

  • Using model binding to parse HTML form submissions automatically

  • Integrating validation into the request-handling pipeline and generating client-side validation scripts

How It All Fits Together

First, it's useful to understand how the MVC Framework's built-in conventions mesh together to help you handle data entry. Figure 12-1 illustrates how the framework provides two-way mapping between server-side .NET model objects and client-side HTML forms.

How ASP.NET MVC's conventions work together to manage data entry

Figure 12-1. How ASP.NET MVC's conventions work together to manage data entry

When you use HTML helpers, they render HTML form elements with names and values corresponding to properties on your model. In a moment you'll learn how to push this process further using templated view helpers—a more powerful conventions-based approach to generating HTML.

The cycle is completed later, when the user posts the HTML form back to your server. The model binding system uses equal and opposite conventions to map the incoming data back on to the same model object types. This symmetry lets your controllers work purely in terms of .NET objects without manually needing to parse incoming data, and the whole cycle creates a lightweight kind of statefulness that means your form elements effectively retain their contents after a validation failure.

Note

This is totally unrelated to how ASP.NET Web Forms does postbacks and maintains control state. ASP.NET MVC's controllers don't follow the Web Forms page life cycle, and its views don't maintain control state using any hidden form field similar to ViewState. Instead, ASP.NET MVC works using rendering and binding conventions that are described in this chapter, all of which you can extend, replace, or ignore if you wish.

Templated View Helpers

All the HTML helpers that you saw in the previous chapter—such as Html.TextBoxFor() and Html.ActionLink()—specify explicitly which type of HTML element they should render. A new feature added in ASP.NET MVC 2, templated view helpers, gives you the further option of saying you want to render a display or an editor for a model object or one of its properties, but without saying what HTML elements it should render. The framework will then choose and render a template according to the type of the model object or property, selecting either a custom template or one of its default built-in templates.

As a simple example, your model object might have a property called Approved. If this property is of type bool, then Html.EditorFor(x => x.Approved) will by default generate a check box with the label "Approved." Or, if Approved is a string, then it will generate a text box with the same label. Or, if Approved is some custom model type, then it will use reflection and iterate over Approved's properties, generating a suitable input element for each property. In each case, the input elements will have names matching the framework's conventions so that the form values can later be mapped back to the same model type.

This sounds very clever, but don't be misled into thinking you can always generate satisfactory user interfaces purely by automatic conventions. When crafting quality software, you'll want to finely tailor each of your application's UIs to aim for the optimal user experience, accounting for subtleties such as design aesthetics and user psychology. If you want to exercise such fine-grained control over the way you transform model data into HTML, views already give you all the power and flexibility you need.

So, if templated view helpers aren't intended to replace your manual control, what are they good for? They're generally used as smarter versions of partial views—rendering fragments within a larger view—and are especially useful in the following situations:

  • Displaying and editing custom types: For example, if your application deals with geospatial data, you might have a custom type called LatLong. Whenever you want to render a display or editor for a model property called Location of the type LatLong, you can call Html.DisplayFor(x => x.Location) or Html.EditorFor(x => x.Location), having already created suitable templates for displaying and editing that data type. This is just a convention-based alternative to manually invoking something like Html.Partial("LatLongEditor.ascx", Model.Location). Later in this chapter, you'll see an example of establishing a convention that DateTime properties should be edited using a certain date picker widget.

  • Editing hierarchical models: If your model objects have properties with subproperties and sub-subproperties, then you need to name all of your HTML form elements according to model binding conventions if you want the user's input to be mapped back to your model automatically. Templated view helpers automatically observe this naming convention, and if you explicitly provide a template for each model type, you can remain in total control over the HTML output.

  • Scaffolding: This term refers to the framework's ability to generate entire display and data entry UIs directly from your model types. Similar to real-world scaffolding that assists builders during construction, these UIs are mainly useful as a temporary solution during development until you have time to replace them with properly customized UIs.

Displaying and Editing Models Using Templated View Helpers

The MVC Framework includes the templated view helpers listed in Table 12-1. You'll find further details about each of them in this chapter.

Table 12-1. Built-In Templated View Helpers

Helper Name

Example

Purpose

Display

Html.Display("Title")

Renders a read-only view of the specified model property, choosing a template according to the property's type and any metadata associated with it.

DisplayFor

Html.DisplayFor(x => x.Title)

Strongly typed version of the previous helper.

DisplayForModel

Html.DisplayForModel()

Shorthand way of writing Html.DisplayFor(x => x). In other words, it renders a read-only view of the entire model object rather than a specific property.

Editor

Html.Editor("Title")

Renders an edit control for the specified model property, choosing a template according to the property's type and any metadata associated with it.

EditorFor

Html.EditorFor(x => x.Title)

Strongly typed version of the previous helper.

EditorForModel

Html.EditorForModel()

Shorthand way of writing Html.EditorFor(x => x). In other words, it renders an edit control for the entire model object rather than for a specific property.

Label

Html.Label("Title")

Renders an HTML <label> element referring to the specified model property.

LabelFor

Html.LabelFor(x => x.Title)

Strongly typed version of the previous helper.

LabelForModel

Html.LabelForModel()

Shorthand way of writing Html.LabelFor(x => x). In other words, it renders an HTML <label> element referring to the entire model object rather than a specific property.

DisplayText

Html.DisplayText("Title")

Bypasses all templates and renders a simple string representation of the specified model property.

DisplayTextFor

Html.DisplayTextFor(x => x.Title)

Strongly typed version of the previous helper.

Tip

Most developers prefer to use the strongly typed (lambda expression-based) versions of these helpers, because they have the advantage of providing IntelliSense. Plus, if you later rename a model property, then a refactoring tool can automatically update all lambda expressions that reference it.

Let's begin our exploration by trying out the most dramatic of these helpers, Html.EditorForModel(). We'll start by defining some model classes, Person and Address.

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public Address HomeAddress { get; set; }
    public bool IsApproved { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
}

As you learned in the previous chapter, an action method could render an instance of Person by invoking a strongly typed view inherited from System.Web.Mvc.ViewPage<Person>. If the view contained the following:

<h2>Edit this person</h2>
<% using(Html.BeginForm()) { %>
    <%: Html.EditorForModel() %>
    <p><input type="submit" value="Save" /></p>
<% } %>

then it would render as a complete user interface, as shown in Figure 12-2.

Example output from the Html.EditorForModel() helper

Figure 12-2. Example output from the Html.EditorForModel() helper

The UI was generated by the framework's built-in default editor template for an arbitrary object. This template uses reflection and iterates over each of the object's simple properties,[72] and for each one it renders a label and a suitable editor for that property. Each of the form fields is named to match the corresponding model property, so when the user posts this form back to your server, the model binding system will automatically reconstitute the data as a Person instance.

You'll learn more about both model binding and the default object editor template shortly. First, let's consider how you could customize and improve this UI.

Using Model Metadata to Influence Templated View Helpers

.NET classes provide only a limited amount of information about the data they contain. They specify the .NET types and names of a set of properties, but by default, that's all they say. They don't give user-friendly names for the properties, they don't say whether a property is only for the software's internal use or whether an end user would be interested in the value, and they don't declare which set of values would be considered valid.

To bridge this gap, ASP.NET MVC uses model metadata to let you provide extra information about the meaning of your models. Model metadata influences how templated view helpers produce displays and editors, and it influences how the framework validates incoming data.

Out of the box, ASP.NET MVC will detect and respond to metadata attributes from the standard .NET System.ComponentModel namespace. If these don't meet your needs, you can create custom metadata attributes, or you can make your own metadata provider by inheriting from the ModelMetadataProvider base class.

As a simple example, you could improve the UI shown in Figure 12-2 by applying metadata attributes to the Person class as follows:

public class Person
{
    [HiddenInput (DisplayValue = false)] // Don't want the user to see or edit this
    public int PersonId { get; set; }

    // [DisplayName] specifies user-friendly names for the properties
    [DisplayName("First name")] public string FirstName { get; set; }
    [DisplayName("Last name")] public string LastName { get; set; }

    [DataType(DataType.Date)] // Show only the date, ignoring any time data
    [DisplayName("Born")] public DateTime BirthDate { get; set; }

    public Address HomeAddress { get; set; }

    [DisplayName("May log in")] public bool IsApproved { get; set; }
}

With this metadata in place, the preceding view would render as shown in Figure 12-3. You'll learn about other standard metadata attributes shortly.

Example output from Html.EditorForModel() after applying metadata

Figure 12-3. Example output from Html.EditorForModel() after applying metadata

Right now, if you were to look at the generated HTML, you'd find it references a useful set of CSS classes, so you could style it easily. Here's what the default object template renders for the FirstName model property (reformatted for readability):

<div class="editor-label">
    <label for="FirstName">First name</label>
</div>
<div class="editor-field">
    <input class="text-box single-line" id="FirstName"
           name="FirstName" type="text" value="Blaise" />
</div>

For example, you could use CSS to make the labels and the input controls appear on the same lines instead of having the labels appear above the input controls.

Rendering Editors for Individual Properties

Instead of using Html.EditorForModel() to render an editor for an entire model object, it's also common to use Html.Editor() or Html.EditorFor() to place editors for individual properties at different locations in a view. This gives you much more control over the finished output, but still uses model metadata and templating conventions to influence the result.

For example, you could express the previous view as follows:

<% using(Html.BeginForm()) { %>
    <fieldset>
        <legend>Person</legend>

        <div class="field">
            <label>Name:</label>
            <%: Html.EditorFor(x => x.FirstName) %>
            <%: Html.EditorFor(x => x.LastName) %>
        </div>
        <div class="field">
            <label>Born:</label>
            <%: Html.EditorFor(x => x.BirthDate) %>
        </div>
        <div align="center"><%: Html.EditorFor(x => x.IsApproved) %>May log in</div>
        <fieldset>
            <legend>Home address</legend>
            <div class="addressEditor">
                <%: Html.EditorFor(x => x.HomeAddress) %>
            </div>
        </fieldset>
    </fieldset>

    <p><input type="submit" value="Save" /></p>
<% } %>

Note

Here, we're using the strongly typed Html.EditorFor() helper, which is only available in strongly typed views. You could get the same result by calling the string-based alternative (i.e., Html.Editor("FirstName")), which is available in loosely typed views, but doesn't have the benefits of IntelliSense and refactoring support.

This would render as shown in Figure 12-4.

Using Html.EditorFor() to add editors to a view

Figure 12-4. Using Html.EditorFor() to add editors to a view

This is now very similar to using simple HTML helpers such as Html.TextBoxFor() and Html.CheckBoxFor(), but with a few advantages:

  • You don't have to specify which input controls should be used. In this example, you can see the framework automatically chooses to use a check box for the bool property.

  • Model metadata is respected. As you can see, the birth date is shown without an associated time because of the [DataType(DataType.Date)] metadata attribute.

  • You can get scaffolding for child properties without extra work. In this example, we've made a basic address editor simply by writing Html.EditorFor(x => x.HomeAddress). Later, you'll see how to supply custom templates to replace the scaffolding.

Rendering Labels for Individual Properties

If you prefer to generate the input control labels directly from model metadata, you can use Html.Label() or Html.LabelFor(). For example, you could render the text label for the IsApproved property as follows:

<%: Html.LabelFor(x => x.IsApproved) %>

This will render as follows:

<label for="IsApproved">May log in</label>

This time, the phrase "May log in" is taken from the [DisplayName("May log in")] metadata attribute instead of being specified in the view. If there was no such metadata, it would fall back on displaying the actual property name.

This isn't necessarily any better than writing the label text directly into a view. After all, views are the normal place in MVC architecture for UI details such as label text. The most likely reason to use Html.Label() or Html.LabelFor() is if you're using the same model class on multiple data entry views—you may want to define the label texts in one central place (i.e., in a [DisplayName] attribute on the model) so it's consistent across all the views.

Tip

You'll learn in Chapter 17 how to display fragments of text from a RESX resource file. This gives you an alternative way to reference text resources defined in a central location, and makes it very easy to support translation into multiple languages. This is usually better than [DisplayName] for large or international applications, since [DisplayName] doesn't directly support any kind of localization.

The Built-in Editor Templates

When you use Html.EditorFor() or a similar helper, ASP.NET MVC needs to choose a suitable template to render. It does so by considering each of the following template names, in this order of priority:

  1. Any template name explicitly specified in your call to the HTML helper—for example, if you call Html.EditorFor(x => x.SomeProperty, "MyTemplate").

  2. Any template name specified by model metadata—for example, if you attach an attribute such as [UIHint("MyTemplate")] to the model property. You'll learn more about [UIHint] and the ModelMetadata.TemplateHint property shortly.

  3. The data type name specified by model metadata—for example, if you attach an attribute such as [DataType("MyDataType")] or [DataType(DataType.EmailAddress)] to the model property. You'll learn more about [DataType] and the ModelMetadata.DataTypeName property shortly.

  4. The actual .NET type name for the chosen model object or property. Note that it unwraps nullable types automatically, so properties of type int, int?, or System.Nullable<System.Int32> will all be mapped to the template name Int32.

  5. If the chosen model object or property is of a "simple" type (i.e., it can be converted from a string using TypeDescriptor.GetConverter, which includes common types such as int and DateTime), then it uses the built-in template name String.

  6. If the chosen model object or property is not an interface, then it tries the names of all base types, up to but excluding Object.

  7. If the chosen model object or property implements IEnumerable, then it uses the built-in template name Collection.

  8. Finally, it falls back on using the built-in template name Object.

For each of these possible template names, the framework will first try to find a template by asking your active view engine for a partial called EditorTemplates/templateName. If one is found, it gets invoked, and the process is complete. You learned in Chapter 9 how the view engine searches for views and partials in the folders /Areas/areaName/Views/controllerName, /Areas/areaName/Views/Shared, /Views/controllerName, and /Views/Shared. Note that these view lookups are cached within each HTTP request, so the scanning process is not as expensive as you might imagine.

If the view engine can't find any partial matching the requested name, the framework will consider using a built-in default editor template. Table 12-2 lists all of the built-in editor templates.

Table 12-2. The Built-In Editor Templates

Template Name

Behavior

Boolean

For regular bool properties, renders a check box input control by calling Html.CheckBox(), adding the CSS class check-box. For nullable bool? properties, renders a drop-down list by calling Html.DropDownList(), giving the options True, False, or empty, adding the CSS class list-box tri-state.

Collection

For IEnumerable models or properties, iterates through the enumerable, rendering the editor template for each item. For each item, the HTML field names will be prefixed with propertyName [zeroBasedIndex]. For example, a property MyProp of type string[] may render as text boxes called MyProp[0], MyProp[1], MyProp[2], and so forth.

Decimal

Renders a text box by called Html.TextBox(), adding the CSS class text-boxsingle-line, and formatting the value to show two decimal places (as you'd normally edit a currency value).

HiddenInput

Renders a hidden input control by calling Html.Hidden(). For properties of type Binary or byte[], the hidden input is populated using a Base64-encoded representation of the binary data. For other property types, the hidden input is populated by calling Convert.ToString() on the property value. Also, this template renders a read-only string display of the property value unless the model metadata's HideSurroundingHtml flag is set (more about this later).

MultilineText

Renders an HTML <textarea> element by calling Html.TextArea(), adding the CSS class text-box multi-line.

Object

See the description just after the end of this table.

Password

Renders a password input control by calling Html.Password(), adding the CSS class text-box single-line password.

String

Renders a text box by calling Html.TextBox(), adding the CSS class text-box single-line.

Text

Behaves exactly like the String template. This alternative name is helpful for responding to the [DataType(DataType.Text)] attribute from System.ComponentModel.

Tip

You can override any of these default templates by creating a custom editor template partial with the same name. For example, you can override the editor template for Boolean by creating a partial view at /Views/Shared/EditorTemplates/Boolean.ascx. You'll learn more about custom templates later in the chapter.

The Object template deserves special attention because it's a little more sophisticated. This is the ultimate fallback template, and is typically what's used during scaffolding, because you'll call Html.EditorFor(x => x.SomeComplexProperty) or Html.EditorForModel() without having defined an editor template for your property or model type.

The Object template iterates over each of your type's properties, rendering a label, an editor, and a validation message for each one (you'll learn more about validation later in this chapter). For each property, it produces HTML of the following form:

<div class="editor-label"><label for="propertyName">property label</label></div>
<div class="editor-field">propertyeditor template goes here</div>

Scaffolding Is Not Recursive

What might surprise you is that first, the default Object template will skip any properties that are not simple (again, this is defined as being convertible from a string using TypeDescriptor.GetConvertor). Otherwise, it might cause unwanted side effects such as triggering lazy-loading.

Second, it can't be invoked from any other template: if you try to invoke the Object template from inside a different template, it will merely display the model property as a simple read-only string and won't iterate through its subproperties. In summary, scaffolding is not recursive.

Displaying Models Using Templated View Helpers

So far, we've only considered the templated view helpers for editing data: Html.Editor(), Html.EditorFor(), and Html.EditorForModel(). Those are the most commonly used, but there are also read-only display equivalents: Html.Display(), Html.DisplayFor(), and Html.DisplayForModel(). The display helpers behave exactly like the editor helpers, except they use a different set of default templates, and they look for template partials in a different folder (they look for partials called DisplayTemplates/templateNameinstead of EditorTemplates/templateName).

The framework's built-in default display templates are listed in Table 12-3.

Table 12-3. The Built-In Display Templates

Template Name

Behavior

Boolean

Just like the equivalent editor template, renders a check box or a drop-down list. The difference is that the control is disabled by adding the HTML attribute disabled="disabled".

Collection

For IEnumerable models or properties, iterates through the enumerable, rendering the display template for each item.

Decimal

Displays the value with two decimal places.

EmailAddress

Renders an HTML mailto: link —for example, <a href="mailto:"></a>.

HiddenInput

Renders a read-only string display of the property value, unless the model metadata's HideSurroundingHtml flag is set (more about this later), in which case it renders nothing.

Html

Renders the property value without HTML-encoding it. This is useful if the property value contains HTML tags that you know are safe to display.

Object

See the explanation that follows this table.

String

Renders the property value after HTML-encoding it.

Text

Behaves exactly like the String template. This alternative name is helpful for responding to the [DataType(DataType.Text)] attribute from System.ComponentModel.

Url

Renders an HTML link tag—for example, <a href="/some/url">/some/url</a>. Note that it won't resolve virtual paths (i.e., URLs beginning with ~/), so the target URL must be either absolute or relative to the current URL. You can override the link text by using a metadata attribute such as [DisplayFormat(DataFormatString = "Click me")].

The built-in Object display template works almost exactly like the equivalent editor template, in that it iterates over simple properties and produces a display for each one. For each property, it generates HTML of the following form:

<div class="display-label">property label</div>
<div class="display-field">property value</div>

Again, it isn't recursive: it won't iterate over your properties if you call it from inside another template.

Continuing the previous example, an action method could render an instance of Person by invoking a strongly typed view inherited from System.Web.Mvc.ViewPage<Person>. If the view contained the following:

<h2>Examine this person</h2>
<%: Html.DisplayForModel() %>

then it would render each of the properties as shown in Figure 12-5.

Output from Html.DisplayForModel()

Figure 12-5. Output from Html.DisplayForModel()

Clearly, this is not a very attractive user interface. You could improve it by applying CSS styles, or by calling Html.DisplayFor() for each property within a more well-structured page instead of calling Html.DisplayForModel(), just like we did with Html.EditorFor().

A further option is to create a custom template for the Person type. Let's move on and consider how to do that.

Using Partial Views to Define Custom Templates

If you're making heavy use of templated view helpers, you'll probably want to go beyond the built-in default editor and display templates (e.g., String, Object, Collection, as listed in Tables 12-3 and 12-4) and create custom templates. Custom templates are nothing more than partial views named and placed in a particular way so the framework can find them as part of the template selection process.

You've already learned how ASP.NET MVC asks your view engine for a partial called EditorTemplates/templateName or DisplayTemplates/templateName, where templateName is an explicitly provided name, a template name given by model metadata, the name of the .NET type, or a built-in default template name. So, if you want to change how Person instances are displayed, you could create a partial at /Views/Shared/DisplayTemplates/Person.ascx, strongly typed with model class Person, containing the following:

<%@ Control Language="C#"
    Inherits="System.Web.Mvc.ViewUserControl<Namespace.Person>" %>
<div class="person">
    <img class="person-icon" src="/content/person-icon.png" />
    <%: Html.ActionLink(Model.FirstName + " " + Model.LastName,
                        "Edit", new { Model.PersonId }) %>
    (born <%: Model.BirthDate.ToString("MMMM dd, yyyy") %>)
    <div class="address">
        <%: Html.DisplayFor(x => x.HomeAddress, "AddressSingleLine") %>
    </div>
</div>

This partial explicitly references another display template called AddressSingleLine, so you'll need to create another partial in the same folder called AddressSingleLine.ascx, perhaps containing the following:

<%@ Control Language="C#"
    Inherits="System.Web.Mvc.ViewUserControl<Namespace.Address>" %>
<%: string.Join(", ",
       new[] {
            Model.Line1, Model.Line2, Model.City, Model.PostalCode, Model.Country
       }.Where(x => !string.IsNullOrEmpty(x)).ToArray()
    ) %>

Now, without any further changes to the original view, the Person instance would be rendered as shown in Figure 12-6 (CSS styles added).

Updated output from the Html.DisplayForModel() helper

Figure 12-6. Updated output from the Html.DisplayForModel() helper

This is not a unique achievement. You could easily set up a similar structure of views and partials without using the templating system, rendering your partials using Html.Partial() or Html.RenderPartial() as described in the previous chapter. The added benefit of using templates for display is that you can establish standard conventions about how certain model types should be rendered, and then you don't have to remember which partials to invoke. For example, you could now render an enumerable collection of Person instances with a single line of view markup—for example:

<%: Html.DisplayFor(x => x.MyPersonCollection) %>

This would render the Person.ascx partial once for each item in the collection. You need to judge whether, for your own application, conventions like this will save you time or merely make it harder to guess what your code will do at runtime.

Creating a Custom Editor Template

You're not limited to defining templates for your own custom model types; you can also define templates for standard .NET types or templates that overwrite the built-in defaults that you saw in Tables 12-3 and 12-4.

For example, you could create a standard editor template for DateTime properties by creating a partial at /Views/Shared/EditorTemplates/DateTime.ascx, containing the following:

<%@ Control Language="C#" Inherits="ViewUserControl<DateTime?>" %>
<%: Html.TextBox("",                                        /* Name suffix     */
                 ViewData.TemplateInfo.FormattedModelValue, /* Initial value   */
                 new { @class = "date-picker" }             /* HTML attributes */
    ) %>

Tip

When creating templates for value types such as int and DateTime, it's wise to set your model type to the nullable equivalent (in this example, DateTime?) because ASP.NET MVC will expect your template to handle both nullable and nonnullable versions of the type. If you don't do this, then if the model property is nullable and holds null, you'll simply get an error message.

Now that this partial exists, all DateTime and DateTime? property editors (everywhere in your application, except where overridden using an explicit template name or a [UIHint] attribute ) will be rendered as text boxes with the CSS class date-picker. We're passing an empty string for the name parameter because the framework will automatically prefix this with the field name corresponding to the model item being rendered; you only need to specify a nonempty value if your editor is for a child property (HTML field prefixes are covered in the next section).

The resulting HTML will be similar to the following:

<input class="date-picker" id="PropertyName" name="PropertyName"
type="text" value="PropertyValue" />

You could then associate all such elements with client-side date picker widgets using a client-side toolkit such as jQuery UI by putting the following script into a site-wide master page:

<script type="text/javascript">
    $(function() {
        $(".date-picker").datepicker(); // Turns matching elements into date pickers
    });
</script>

See jQuery UI's web site at http://jqueryui.com/ for more details about how to install and use it.

Respecting Formatting Metadata and Inheriting from ViewTemplateUserControl<T>

Since custom display or editor templates are usually implemented as strongly typed partial views, you can access the view's Model property to obtain the value of the current model item. But when you want to render that model item as a string, don't directly call Model.ToString(), as that would bypass any formatting metadata on the model. Instead, notice how the preceding sample code for DateTime.ascx represents the model item as a string using ViewData.TemplateInfo.FormattedModelValue—this respects formatting metadata, so this custom template behaves correctly when combined with [DataType(DataType.Date)] or the [DisplayFormat] attribute .

As a convenient simplification, ASP.NET MVC offers a specialized base class for display and editor templates. If you edit a partial view's Inherits directive so that the partial inherits from ViewTemplateUserControl<modelType>, then you'll have access to a new property, FormattedModelValue, which is shorthand for ViewData.TemplateInfo.FormattedModelValue.

For example, the preceding DateTime.ascx sample could be rewritten as follows:

<%@ Control Language="C#" Inherits="ViewTemplateUserControl<DateTime?>" %>
<%: Html.TextBox("",                             /* Name suffix     */
                 FormattedModelValue,            /* Initial value   */
                 new { @class = "date-picker" }  /* HTML attributes */
    ) %>

Passing Additional View Data to Custom Templates

Sometimes you might want to pass more than just the model to your custom template; you might also want to pass some additional parameters that influence how it should render the model.

This is easy—all the template-rendering helper methods (Editor, EditorFor, EditorForModel, Display, DisplayFor, DisplayForModel) have overloads that let you pass a parameter called additionalViewData. If you pass an anonymously typed object for this parameter, the framework will extract your object's properties and use them to populate your custom template's ViewData dictionary.

For example, you might have a special date picker that handles time zones in some way. You could render a date editor as follows:

<%: Html.EditorFor(x => x.BirthDate, new { timezone = "PST" }) %>

and then your custom DateTime.ascx template would be able to receive the time zone parameter by reading it from ViewData["timezone"].

Working with HTML Field Prefixes and the TemplateInfo Context

When editing data (as opposed to displaying a read-only view of it), there is a further benefit that comes with using templates rather than merely invoking partials directly. Templates introduce a notion of HTML field prefixes. When you render a hierarchy of templates nested inside other templates, the framework automatically builds up a string expression that uniquely describes your current location in the hierarchy. This provides two main benefits:

  • All of the built-in HTML helpers (e.g., Html.TextBox()) respect this, prefixing the current value before any name or id attribute they render. This makes it much easier to keep all of your input elements' IDs unique, as required by the HTML specification.

  • The generated string expression describes the referenced property's location in the model object graph. For example, if you call <%: Html.EditorFor(x => x. MyPersonCollection[2]) %>, which in turn calls <%: Html.EditorFor(x => x.HomeAddress) %>, then the address template will prefix its input element names with MyPersonCollection[2].HomeAddress. This exactly matches model binding conventions, so all the posted data can be reconstituted as a collection of People instances. In a sense, this algorithm is the inverse of ViewData.Eval()—it works out what string expression is necessary to reach each property.

With ASP.NET MVC 1.0, you had to manage HTML field prefixes manually. With the new templating system, it happens automatically. As a basic example of this feature at work, refer back a few pages to Figure 12-4, which shows a Person editor that contains an Address editor. The text box labeled "City" was rendered as the following HTML:

<input class="text-box single-line" id="HomeAddress_City
       name="HomeAddress.City" type="text" value="Paris" />

As you can see, the framework has prefixed the element's name with HomeAddress. The same applies to its id, except that to satisfy the HTML 4.01 specification, it replaces characters other than letters, digits, dashes, underscores, and colons with HtmlHelper.IdAttributeDotReplacement, which equals underscore (_) unless you overwrite it.

When you're creating custom templates, you may want to follow these element name prefixing conventions. You can do that easily by using the properties and methods on ViewData.TemplateInfo, as listed in Table 12-4.

Table 12-4. Properties and Methods on ViewData.TemplateInfo

Name

Purpose

FormattedModelValue

Returns a simple string representation of the current model item, respecting any formatting metadata such as the [DisplayFormat] attribute.

GetFullHtmlFieldId

Generates a valid HTML element ID value based on the current field prefix and a supplied ID suffix. For example, GetFullHtmlFieldId("City") may return HomeAddress_City.

GetFullHtmlFieldName

Generates an HTML element name value based on the current field prefix and a supplied name suffix. For example, GetFullHtmlFieldName("City") may return HomeAddress.City.

HtmlFieldPrefix

Returns the current field prefix.

TemplateDepth

Returns an int value describing the current depth of the template stack. For example, this is 0 outside all templates, and 1 inside the first template to be rendered from a view.

Visited

Returns a bool value describing whether the supplied ModelMetadata instance has already been rendered during this request by the templating system. You're unlikely to use this; it's mainly used internally by the framework to detect and escape from circular references.

Note

When rendering ASP.NET MVC's built-in HTML helpers (such as Html.TextBox()) from custom templates, you don't need pass prefix information to them. They automatically obtain and render the correct prefixes using ViewData.TemplateInfo. You only need to handle this manually if you're constructing HTML tags manually.

Model Metadata

As you've already seen in this chapter, ASP.NET MVC allows model objects to declare metadata that will influence how the framework displays and edits those model objects. For example, you can attach the attribute [DisplayName("First name")] to a model property, and then the templated view helpers will use your supplied text whenever they render a label for that property.

Just like most of ASP.NET MVC's core mechanisms, the model metadata system is completely pluggable. The framework includes a comprehensive implementation, but also allows you to extend or completely replace it if you wish. Figure 12-7shows how this extensibility works.

The ModelMetadata extensibility architecture

Figure 12-7. The ModelMetadata extensibility architecture

The templated view helpers don't know anything about Data Annotations or any other specific metadata source. They only understand ModelMetadata objects, which are ASP.NET MVC's standard way to describe metadata. Over the next few pages, you'll learn

  • Which Data Annotations attributes make a difference to ASP.NET MVC

  • How DataAnnotationaModelMetadataProvider, the default metadata provider, maps these attributes onto ModelMetadata properties

  • How you can extend or replace DataAnnotationaModelMetadataProvider with your own custom metadata provider

  • How ModelMetadata properties affect the templated view helpers

Working with Data Annotations

Using the .NET Framework's standard Data Annotations attributes in the System.ComponentModel namespace and the System.ComponentModel.DataAnnotations.dll assembly isn't the only way of defining model metadata, but it's the easiest because that's what the MVC Framework supports without any extra work on your part.

Table 12-5 shows which Data Annotations attributes make a difference to DataAnnotationsModelMetadataProvider, how it maps them onto ModelMetadata properties, and what effects these properties trigger.

Table 12-5. System.ComponentModel Attributes Recognized by DataAnnotationsModelMetadataProvider

Data Annotations Attribute

Effect

[DisplayColumn]

Determines which child property Html.DisplayText() and Html.DisplayTextFor() should use to generate a simple string representation of this item. Maps to ModelMetadata's SimpleDisplayText property.

[UIHint]

Affects template selection when rendering displays or editors for this item. Maps to ModelMetadata's TemplateHint property. Note that if a property has multiple [UIHint] attributes, the MVC Framework will give priority to one declared as [UIHint(templateName, PresentationLayer="MVC")].

[DataType]

Affects template selection and how the built-in HTML helpers format the model value as text. Maps to ModelMetadata's DataTypeName, DisplayFormatString, and EditFormatString properties. For example, [DataType(DataType.Date)] sets DataTypeName to "Date" and both format strings to {0:d}.

[ReadOnly]

Maps to ModelMetadata's IsReadOnly property, although this doesn't affect any built-in templated view helper. ASP.NET MVC's DefaultModelBinder will notice a [ReadOnly] attribute (but not the IsReadOnly metadata property!) and will respond to this by not binding any new values for the associated property.

[DisplayFormat]

Affects how the built-in HTML helpers represent the model value as text. For example, the format string {0:c} causes numerical values to be rendered as currency values. Maps to ModelMetadata's DisplayFormatString and EditFormatString properties.

[ScaffoldColumn]

Controls whether the built-in Object templates should show this property. Maps to ModelMetadata's ShowForDisplay and ShowForEdit properties.

[DisplayName]

Affects all built-in helpers that render property labels, including Html.Label(), the built-in Object template, and helpers that display validation messages. Maps to ModelMetadata's DisplayName property.

[Required]

Causes the built-in DataAnnotationsValidatorProvider to validate this property as a required field. Maps to ModelMetadata's IsRequired property.

In addition, ASP.NET MVC includes its own extra metadata attribute, [HiddenInput], that affects HideSurroundingHtml and TemplateHint, as described in Table 12-6. This extra attribute is needed because no equivalent metadata attribute exists in System.ComponentModel.

Creating a Custom Metadata Provider

If Data Annotations attributes and DataAnnotationsModelMetadataProvider don't meet your needs, you can create a custom metadata provider by creating a class that inherits from one of the following base classes:

  • ModelMetadataProvider: The abstract base class for all metadata providers.

  • AssociatedMetadataProvider: Usually a better choice of base class for a custom metadata provider. It deals with much of the tricky work related to obtaining the list of attributes associated with each model property, and transparently fetches these attributes from "buddy" classes configured via [MetadataType]. All you have to do is override a single method, CreateMetadata(), and return a ModelMetadata instance based on a supplied set of attributes, a model type, a property name, and so on.

  • DataAnnotationsModelMetadataProvider: The default metadata provider. By inheriting from this, you can retain support for standard System.ComponentModel Data Annotation attributes. Again, you only need to override its CreateMetadata() method.

As an example, let's consider enhancing the default metadata provider so that it recognizes some extra naming conventions. You may have model classes similar to the following:

public class StockTradingRecord
{
    public string SymbolName { get; set; }
    public DateTime TradingDate { get; set; }
    public decimal ClosingPrice { get; set; }
    public decimal HighPrice { get; set; }
    public decimal LowPrice { get; set; }
}

and wish to set up conventions so that any property whose name ends with Date will be displayed and edited as a date (ignoring any time component of the DateTime value ), and any property whose name ends with Price will be displayed as a currency value.

Since ASP.NET MVC only lets you enable one metadata provider at a time, the easiest way to add extra behavior without losing existing behavior is to inherit from an existing provider. Here's a custom provider that inherits from DataAnnotationsModelMetadataProvider:

public class ConventionsMetadataProvider: DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attribs,
                                    Type containerType, Func<object> modelAccessor,
                                    Type modelType, string propertyName)
    {
        var metadata = base.CreateMetadata(attribs, containerType, modelAccessor,
                                           modelType, propertyName);

        if (propertyName != null) {
            if (propertyName.EndsWith("Date"))
metadata.DisplayFormatString = "{0:d}"; // Date format
            else if (propertyName.EndsWith("Price"))
                metadata.DisplayFormatString = "{0:c}"; // Currency format
        }

        return metadata;
    }
}

To activate this custom metadata provider, assign an instance of it to the static ModelMetadataProviders.Current property. For example, alter Application_Start() in Global.asax.cs as follows:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ModelMetadataProviders.Current = new ConventionsMetadataProvider();
}

Now our new conventions will apply. For example, StockTradingRecord's ClosingPrice property will automatically be displayed as a currency value (i.e., with two decimal places and a currency symbol) whenever rendered by Html.DisplayFor().

Alternatively, you could create a metadata provider that detects and responds to custom metadata attributes, or loads metadata settings from a configuration file, or recognizes a range of custom model types and uses alternative logic to associate matching properties with particular editor or display templates (you can override template selection by assigning a value to ModelMetadata's TemplateHint property). Let's now consider the full range of ModelMetadata properties that you can set using a custom metadata provider.

The Full Set of Metadata Options

At the risk of duplicating information that you could find online on MSDN, Table 12-6 gives a complete list of the writable properties you can set on a ModelMetadata instance to influence the templating system. You can write to these properties either from a custom metadata provider, as shown in the previous example, or in many cases using built-in System.ComponentModel attributes, as described in the following table.

The reason I include this complete list is that, at the time of writing, MSDN does not provide such clear information about how each property affects ASP.NET MVC's behavior. As you will see, not all of these properties are actively used by ASP.NET MVC 2.

Table 12-6. Public Writable Properties on ModelMetadata

Member

Meaning

Affects/Affected By

AdditionalValues

Dictionary of arbitrary extra metadata values for use by custom providers.

Not set or used by any built-in part of ASP.NET MVC, but as explained later, can be used by custom metadata providers and HTML helpers.

ConvertEmptyStringToNull

Boolean value; true by default. If true, the default model binder will replace any incoming empty string values for this property with null.

Can be set using [DisplayFormat]. Affects model binding.

DataTypeName

Provides further information about the intended meaning of the property—for example, specifying whether it's a string that holds an e-mail address (DataType.EmailAddress) or preencoded HTML (DataType.Html).

Can be set using [DataType]. Affects template selection (see the algorithm described in the section "The Built-In Editor Templates" earlier in this chapter). Certain special values (e.g., DataType.Date and DataType.Currency) affect DisplayFormatString and EditFormatString.

Description

Holds a human-readable description of the model item.

Not set or used by any built-in part of ASP.NET MVC.

DisplayFormatString

Holds a formatting string (e.g., "{0:yyyy-MM-dd}") that determines how ViewData.TemplateInfo.FormattedModelValue is populated with a string representation of the model object.

Can be set using [DisplayFormat(DataFormatString=...)]. Affects how the built-in display templates represent the model value as text.

DisplayName

Provides a human-readable name for the property.

Can be set using [DisplayName]. Affects all built-in helpers that render property labels, including Html.Label(), the built-in Object template, and helpers that display validation messages.

EditFormatString

Just like DisplayFormatString, except it applies when rendering editor templates.

Can be set using [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = ...)]. Affects how the model value is represented as text inside text boxes and similar controls.

HideSurroundingHtml

Describes whether scaffolding should render a label alongside this property's display or editor. false by default.

Can be set to true using [HiddenInput(DisplayValue=false)]. If true, the Object template and the HiddenInput template will avoid rendering any extra HTML around the display or editor.

IsReadOnly

Specifies whether the item should be treated as read-only.

Can be set using [ReadOnly(true)]. IsReadOnly doesn't affect the behavior or appearance of any of the built-in HTML helpers, but the [ReadOnly] Data Annotations attribute tells the default model binder not to accept any new values for this item.

IsRequired

Specifies whether the item should be validated as a required field. By default, this is true if and only if the property type cannot hold null values.

Can be set to true using [Required]. If true, DataAnnotationsValidatorProvider (covered later in this chapter) will automatically add a required field validator for the property.

Model

Gets or sets the model object or property value that this ModelMetadata instance describes.

Automatically set when rendering any template, and used by most HTML helpers to determine an initial value.

NullDisplayText

Specifies the string to be displayed in place of a null value.

Can be set using [DisplayFormat(NullDisplayText=...)]. Affects the output of Html.DisplayText() and Html.DisplayTextFor().

Provider

Gets or sets the ModelMetadataProvider instance associated with this metadata object.

Set automatically by the ModelMetadata constructor. Used when the framework must recursively obtain details of child properties.

ShortDisplayName

A shorter human-readable description of the model item.

Not set or used by any built-in part of ASP.NET MVC.

ShowForDisplay

Specifies whether the item should be included in scaffolded displays. true by default.

Can be set using [ScaffoldColumn(...)]. If false, the default Object display template will skip this property.

ShowForEdit

Specifies whether the item should be included in scaffolded editors. true by default.

Can be set using [ScaffoldColumn(...)]. If false, the default Object editor template will skip this property. Note that there's no built-in way to set ShowForDisplay and ShowForEdit independently; they both take the same value from [ScaffoldColumn].

SimpleDisplayText

Provides a simple string representation of the model item. Takes its default value from Model.ToString() if overridden, otherwise uses an arbitrary property from Model, or uses NullDisplayText if Model is null.

You can control which child property value is used as the default SimpleDisplayText value using [DisplayColumn]. Affects the output of Html.DisplayText() and Html.DisplayTextFor().

TemplateHint

Specifies the name of the template that should be used when rendering displays or editors for this item.

Can be set to an arbitrary value using [UIHint(...)], or can be set to the special value HiddenInput using [HiddenInput]. Affects template selection (see the algorithm described in the section "The Built-in Editor Templates" earlier in this chapter).

Watermark

Specifies text that could be overlaid onto empty input controls to act as a prompt for the user.

Not set or used by any built-in part of ASP.NET MVC.

Tip

If you want to add extra metadata properties to those normally stored by ModelMetadata, your custom metadata provider can add arbitrary entries to the ModelMetadata instance's AdditionalValues dictionary. Then you can access those additional values from a custom HTML helper.

Consuming Model Metadata in Custom HTML Helpers

Any HTML helper can access the metadata associated with the model object or property that it is currently rendering. ModelMetadata has two static methods, FromStringExpression() and FromLambdaExpression(), that retrieve the desired ModelMetadata instance from ViewData.

For example, you might want the IsReadOnly metadata flag to cause text boxes to render in a disabled state (so the user can read but not edit the value). You could create your own wrapper around Html.TextBoxFor() that does this.

public static class EnhancedTextBoxExtensions
{
    public static MvcHtmlString TextBoxForEx<T, TProp>(this HtmlHelper<T> html,
                                                Expression<Func<T, TProp>> expr)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expr, html.ViewData);
        bool isReadOnly = metadata.IsReadOnly;
        var htmlAttributes = isReadOnly ? new { disabled = "disabled" } : null;
        return html.TextBoxFor(expr, htmlAttributes);
    }
}

In your views, you can now write Html.TextBoxForEx(x => x.SomeProperty), and the resulting text box will have the HTML attribute disabled="disabled" if the referenced property has a [ReadOnly] attribute.

Html.TextBoxForEx() is an extension method, so it will only be available in views once you've referenced its namespace. See Chapter 11's coverage of custom HTML helpers for more details.

Using [MetadataType] to Define Metadata on a Buddy Class

So far, our approach to defining metadata has relied mainly on Data Annotations attributes. This usually works well, but there's a complication if you can't edit your model classes' source code, perhaps because they're automatically generated by a tool such as the LINQ to SQL or Entity Framework designer. If you can't edit the model's source code, how can you add attributes to its properties?

The solution is to define its metadata on a separate class, known as a buddy class. This buddy class has no behavior and is used only to define metadata. The only requirement is that your real model class has to be defined as a partial class (fortunately, the LINQ to SQL and Entity Framework code generators do mark their entity classes as partial).

Continuing the earlier example with the Person class, you could remove all the metadata attributes, defining it simply as follows:

public partial class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public Address HomeAddress { get; set; }
    public bool IsApproved { get; set; }
}

Notice, of course, that it's now marked as partial. Next, you can separately declare a buddy class as follows, using the [MetadataType] attribute to indicate that it should merge in metadata from properties on another class.

// Note: Must be in the same namespace as the other part of the partial definition
[MetadataType(typeof(PersonMetadata))]
public partial class Person
{
    // This class is only used as a source of metadata
    private class PersonMetadata
    {
        [HiddenInput(DisplayValue = false)] public int PersonId { get; set; }
        [DisplayName("First name")] public string FirstName { get; set; }
        [DisplayName("Last name")] public string LastName { get; set; }

        // Also add any other properties for which you want to supply metadata
    }
}

At runtime, any metadata provider that inherits from AssociatedMetadataProvider (and this includes ASP.NET MVC's built-in default metadata provider) will recognize and respect the [MetadataAttribute] on the partial class, and will use metadata from any properties with matching names on the nominated buddy class (i.e., PersonMetadata). This means your real model class doesn't have to know anything about metadata, and it can safely be regenerated by a tool without losing your attributes.

Model Binding

Each time your site visitors submit an HTML form, your application receives an HTTP request containing the form's data as a set of name/value pairs. You could manually pick out each data item that you wish to receive (e.g., retrieving Request.Form["phoneNumber"]), but this is labor intensive, especially if an action method needs to receive many data items and use them to construct or update a model object.

Model binding is ASP.NET MVC's mechanism for mapping HTTP request data directly into action method parameters and custom .NET objects (including collections). As you'd expect from ASP.NET MVC, it defines certain naming conventions to let you quickly map complex data structures without having to specify all the mapping rules manually.

Model-Binding to Action Method Parameters

You've already been using the framework's model binding feature, every time your action methods have taken parameters—for example:

public ActionResult RegisterMember(string email, DateTime dateOfBirth)
{
    // ...
}

To execute this action method, the MVC Framework's built-in ControllerActionInvoker uses a component called DefaultModelBinder and several implementations of IValueProvider to convert incoming request data into a suitable .NET object for each action method parameter. Over the next few pages, you'll learn in detail how this works.

An IValueProvider represents a supply of raw data arriving with an HTTP request. By default, the framework is configured to use the four value providers listed in Table 12-7, in the order of priority shown.

Table 12-7. Where Model Binding by Default Gets Its Raw Incoming Data (in Priority Order)

Value Provider

Retrieves Data From

How It Interprets String Values

FormValueProvider

Request.Form (i.e., POST parameters)

Culture sensitive (CultureInfo.CurrentCulture)

RouteDataValueProvider

RouteData.Values (i.e., curly brace routing parameters plus defaults)

Culture insensitive (CultureInfo.InvariantCulture)

QueryStringValueProvider

Request.QueryString (i.e., query string parameters)

Culture insensitive (CultureInfo.InvariantCulture)

HttpFileCollectionValueProvider

Request.Files (i.e., uploaded files)

n/a

So, the previous example's email parameter would be populated from

  1. Request.Form["email"], if it exists

  2. Otherwise, RouteData.Values["email"], if it exists

  3. Otherwise, Request.QueryString["email"], if it exists

  4. Otherwise, Request.Files["email"], if it exists (although you would need to change the parameter type from string to HttpPostedFileBase in order to receive the uploaded file)

  5. Otherwise, null

The equivalent is true for the dateOfBirth parameter, but with two differences:

A DateTime value can't be null, so if locations 1 through 4 were all empty, the framework would just throw an InvalidOperationException saying, "The parameters dictionary contains a null entry for parameter 'dateOfBirth' of nonnullable type 'System.DateTime'."

If dateOfBirth were populated from the request URL (locations 2 or 3), then it would be marked for culture-insensitive parsing, so you should use the universal date format yyyy-mm-dd. If it were populated from the form POST data (location 1), then it would be marked for culture-sensitive parsing, leading to different interpretations depending on server settings. A thread in US culture mode would accept the date format mm-dd-yyyy, whereas a thread in UK culture mode would assume dd-mm-yyyy (both would still work fine with yyyy-mm-dd).[73] The reason for this difference of behavior is that it makes sense to interpret user-supplied data culture-sensitively, and form fields are often used to accept such user-supplied data. However, by definition, query string and routing parameters in a universal resource locator (URL) should not contain culture-specific formatting.

The framework's DefaultModelBinder takes these supplies of raw data, most of which are simply string values from the HTTP request, and converts them into whatever .NET objects are required as action method parameters. It uses .NET's type converter facility to deal with converting to simple types such as int and DateTime. But for collections and custom types, something more sophisticated is required.

Model-Binding to Custom Types

You can simplify some action methods tremendously by receiving custom types as parameters, rather than instantiating and populating them manually.

First, let's define a new simple model class as follows:

public class Person
{
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime DateOfBirth { get; set; }
}

Next, consider the following view, which renders a basic user registration form:

<% using(Html.BeginForm("RegisterMember", "Home")) { %>
    <div>Name: <%: Html.TextBox("myperson.Name") %></div>
    <div>Email address: <%: Html.TextBox("myperson.Email") %></div>
<div>Date of birth: <%: Html.TextBox("myperson.DateOfBirth") %></div>

    <input type="submit" />
<% } %>

This form might post to the following action method, which uses no model binding at all:

public ActionResult RegisterMember()
{
    var myperson = new Person();
    myperson.Name = Request["myperson.Name"];
    myperson.Email = Request["myperson.Email"];
    myperson.DateOfBirth = DateTime.Parse(Request["myperson.DateOfBirth"]);

    // ... now do something with myperson
}

There's a lot of tedious plumbing in there, but you can eliminate it as follows:

public ActionResult RegisterMember(Person myperson)
{
    // ... now do something with myperson
}

When DefaultModelBinder is asked to supply an object of some custom .NET type rather than just a simple type like string or int, it uses reflection to determine what public properties are exposed by that custom type. Then it calls itself recursively to obtain a value for that property. This recursion makes it possible to populate an entire custom object graph in one shot.

Notice the naming convention used to match request items with object properties: by default, it looks for values called nameOfParameter.nameOfProperty (e.g., myperson.Email). That ensures it can assign incoming data to the correct parameter object. As recursion continues, the binder would look for nameOfParameter.nameOfProperty.nameOfSubProperty, and so on. This is the same naming convention that templated view helpers use when giving names to the HTML fields they render, so in effect you get two-way binding between model objects and HTML forms.

Tip

When DefaultModelBinder needs to instantiate custom object types (e.g., Person in the previous example), it uses .NET's Activator.CreateInstance() method, which relies on those types having public parameterless constructors. If your types don't have parameterless constructors, or if you want to instantiate them using a DI container, then you can derive a subclass of DefaultModelBinder, override its virtual method CreateModel(), and then assign an instance of your custom binder to ModelBinders.Binders.DefaultBinder. Alternatively, you can implement a custom binder just for that specific type. An example of a custom binder follows shortly.

Now let's consider some ways in which this binding algorithm can be customized.

Specifying a Custom Prefix

In the previous example, the default binder expected to populate the myperson parameter by asking the value provider for myperson.Name, myperson.Email, and myperson.DateOfBirth (which in turn requests data from the value providers listed in Table 12-7). As you can guess, the prefix myperson is determined by the name of the action method parameter.

If you wish, you can specify an alternative prefix using the [Bind] attribute —for example:

public ActionResult RegisterMember([Bind(Prefix = "newuser")]Person myperson)
{
    // ...
}

Now the value provider will be asked for newuser.Name, newuser.Email, and newuser.DateOfBirth. This facility is mainly useful if you don't want your HTML element names to be constrained by what's appropriate for C# method parameter names.

Omitting a Prefix

If you prefer, you can avoid using prefixes altogether. In other words, simplify your view markup by removing the myperson. prefix from each text box name, or if you're using a strongly typed view, use the strongly typed Html.TextBoxFor() helper instead:

<% using(Html.BeginForm("RegisterMember", "Home")) { %>
    <div>Name: <%: Html.TextBoxFor(x => x.Name) %></div>
    <div>Email address: <%: Html.TextBoxFor(x => x.Email) %></div>
    <div>Date of birth: <%: Html.TextBoxFor(x => x.DateOfBirth) %></div>

    <input type="submit" />
<% } %>

The e-mail input text box will now be named Email rather than myperson.Email (and likewise for the other input controls). The incoming values will successfully bind against an action method defined as follows:

public ActionResult RegisterMember(Person myperson)
{
    // ...
}

This works because DefaultModelBinder first looks for values with prefixes inferred from the method parameter name (or from any [Bind] attribute, if present). In this example, that means it will look for incoming key/value pairs whose key is prefixed by myperson. If no such incoming values can be found—and in this example they won't be—then it will try looking for incoming values again, but this time without using any prefix at all.

Choosing a Subset of Properties to Bind

Imagine that the Person class, as used in the last few examples, had a bool property called IsAdmin. You might want to protect this property from unwanted interference. However, if your action method uses model binding to receive a parameter of type Person, then a malicious user could simply append ?IsAdmin=true to the URL used when submitting the member registration form, and the framework would happily apply this property value to the new Person object created.

Clearly, that would be a bad situation. And besides security, there are plenty of other reasons why you might want to control exactly which subset of properties are subject to model binding. There are two main ways to do this.

First, you can specify a list of properties to include in binding by using a [Bind] attribute on your action method parameter—for example:

public ActionResult RegisterMember([Bind(Include = "Name, Email")]Person myperson)
{
    // ...
}

Or you can specify a list of properties to exclude from binding:

public ActionResult RegisterMember([Bind(Exclude = "DateOfBirth")]Person myperson)
{
    // ...
}

Second, you can apply a [Bind] attribute to the target type itself. This rule will then apply globally, across all your action methods, whenever that type is model bound—for example:

[Bind(Include = "Email, DateOfBirth")]
public class Person
{
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime DateOfBirth { get; set; }
}

Which of these strategies you use will depend on whether you're establishing a global rule or a rule that applies just to one particular model binding occasion.

In either case, using an Include rule sets up a whitelist: only the specified properties will be bound. Using an Exclude rule sets up a blacklist: all properties will be bound, except those specifically excluded. It rarely makes sense to specify both Include and Exclude, but if you do, properties will be bound only if they are present in the include list and are not present in the exclude list.

If you use [Bind] on both the action method parameter and the target type itself, properties will be bound only if they're allowed by both filters. So, if you exclude IsAdmin on the target type, that can't be overridden by any action method. Phew!

Invoking Model Binding Directly

You've seen how model binding happens automatically when your action method accepts parameters. It's also possible to run model binding manually. This gives you more explicit control over how model objects are instantiated, where incoming data is retrieved from, and how parsing errors are handled.

For example, you could rewrite the previous example's RegisterMember() action, invoking model binding manually by calling the controller base class's UpdateModel() method as follows:

public ActionResult RegisterMember()
{
    var person = new Person();
    UpdateModel(person);
    // Or if you're using a prefix: UpdateModel(person, "myperson");

    // ... now do something with person
}

This approach is beneficial if you need to control exactly how your model objects are instantiated. Here, you're supplying a Person instance to be updated (which you might have just loaded from a database) instead of letting the framework always create a new Person.

UpdateModel() accepts various parameters to let you choose the incoming data key prefix, which parameters should be included in or excluded from binding, and which value provider supplies incoming data. For example, instead of accepting data from all your registered value providers (which by default are those listed in Table 12-7), you could use the special FormCollection value provider, which gets its data only from Request.Form. Here's how:

public ActionResult RegisterMember(FormCollection form
{
    var person = new Person();
    UpdateModel(person, form);

    // ... now do something with person
}

This permits an elegant way of unit testing your model binding. Unit tests can run the action method, supplying a FormCollection containing test data, with no need to supply a mock or fake request context. It's a pleasingly "functional" style of code, meaning that the method acts only on its parameters and doesn't touch external context objects.

Dealing with Model Binding Errors

Sometimes users will supply values that can't be assigned to the corresponding model properties, such as invalid dates, or text for int properties. To understand how the MVC Framework deals with such errors, consider the following design goals:

  • User-supplied data should never be discarded outright, even if it is invalid. The attempted value should be retained so that it can reappear as part of a validation error.

  • When there are multiple errors, the system should give feedback about as many errors as it can. This means that model binding cannot bail out when it hits the first problem.

  • Binding errors should not be ignored. The programmer should be guided to recognize when they've happened and provide recovery code.

To comply with the first principle, the framework needs a temporary storage area for invalid attempted values. Otherwise, since invalid dates can't be assigned to a .NET DateTime property, invalid attempted values would be lost. This is why the framework has a temporary storage area known as ModelState. ModelState also helps to comply with the second principle: each time the model binder tries to apply a value to a property, it records the name of the property, the incoming attempted value (always as a string), and any errors caused by the assignment. Finally, to comply with the third principle, if ModelState has recorded any errors, then UpdateModel() finishes by throwing an InvalidOperationException saying, "The model of type typename could not be updated."

So, if binding errors are a possibility, you should catch and deal with the exception—for example:

public ActionResult RegisterMember()
{
    var person = new Person();
    try
    {
        UpdateModel(person);
// ... now do something with person
    }
    catch (InvalidOperationException ex)
    {
        // To do: Provide some UI feedback based on ModelState
    }
}

This is a fairly sensible use of exceptions. In .NET, exceptions are the standard way to signal the inability to complete an operation (and are not reserved for critical, infrequent, or "exceptional" events, whatever that might mean[74]). However, if you prefer not to deal with an exception, you can use TryUpdateModel() instead. It doesn't throw an exception, but returns a bool status code—for example:

public ActionResult RegisterMember()
{
    var person = new Person();
    if (TryUpdateModel(person))
    {
        // ... now do something with person
    }
    else
    {
        // To do: Provide some UI feedback based on ModelState
    }
}

You'll learn how to provide suitable UI feedback in the "Validation" section later in this chapter.

Note

When a certain model property can't be bound because the incoming data is invalid, that doesn't stop DefaultModelBinder from trying to bind the other properties. It will still try to bind the rest, which means that you'll get back a partially updated model object.

When you use model binding implicitly—that is, receiving model objects as method parameters rather than using UpdateModel() or TryUpdateModel()—then it will go through the same process, but it won't signal problems by throwing an InvalidOperationException. You can check ModelState.IsValid to determine whether there were any binding problems, as I'll explain in more detail shortly.

Model-Binding to Arrays, Collections, and Dictionaries

One of the best things about model binding is how elegantly it lets you receive multiple data items at once. For example, consider a view that renders multiple text box helpers with the same name:

Enter three of your favorite movies: <br />
<%: Html.TextBox("movies") %><br />
<%: Html.TextBox("movies") %><br />
<%: Html.TextBox("movies") %>

Now, if this markup is in a form that posts to the following action method:

public ActionResult DoSomething(List<string> movies)
{
    // ...
}

then the movies parameter will contain one entry for each corresponding form field. Instead of List<string>, you can also choose to receive the data as a string[] or even an IList<string>—the model binder is smart enough to work it out. If all of the text boxes were called myperson.Movies, then the data would automatically be used to populate a Movies collection property on an action method parameter called myperson.

Model-Binding Collections of Custom Types

So far, so good. But what about when you want to bind an array or collection of some custom type that has multiple properties? For this, you'll need some way of putting clusters of related input controls into groups—one group for each collection entry. DefaultModelBinder expects you to follow a certain naming convention that is best understood through an example.

Consider the following view markup:

<% using(Html.BeginForm()) { %>
    <h2>First person</h2>
    <div>Name: <%: Html.TextBox("people[0].Name") %></div>
    <div>Email address: <%: Html.TextBox("people[0].Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[0].DateOfBirth")%></div>

    <h2>Second person</h2>
    <div>Name: <%: Html.TextBox("people[1].Name")%></div>
    <div>Email address: <%: Html.TextBox("people[1].Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[1].DateOfBirth")%></div>

    ...
    <input type="submit" />
<% } %>

Check out the input control names. The first group of input controls all have a [0] index in their name; the second all have [1]. To receive this data, simply bind to a collection or array of Person objects, using the parameter name people—for example:

[HttpPost]
public ActionResult RegisterPersons(IList<Person> people)
{
    // ...
}

Because you're binding to a collection type, DefaultModelBinder will go looking for groups of incoming values prefixed by people[0], people[1], people[2], and so on, stopping when it reaches some index that doesn't correspond to any incoming value. In this example, people will be populated with two Person instances bound to the incoming data.

An easier way of generating input controls with correctly indexed names is to use a for loop and lambda-based HTML helpers. For example, if your Model object has a property called People of type IList<People>, you can render a series of input control groups as follows:

<% for (var i = 0; i < Model.People.Count; i++) { %>
    <h2>Some person</h2>
    <div>Name: <%: Html.TextBoxFor(x => x.People[i].Name)%></div>
    <div>Email address: <%: Html.TextBoxFor(x => x.People[i].Email)%></div>
    <div>Date of birth: <%: Html.TextBoxFor(x => x.People[i].DateOfBirth)%></div>
<% } %>

If you want an even easier way to do it, you can use templated view helpers. The built-in Collection template automatically iterates over collections and renders a suitably indexed display or editor for each item. If you define a partial at /Views/controllerName/EditorTemplates/Person.ascx containing the following:

<%@ Control Language="C#"
            Inherits="System.Web.Mvc.ViewUserControl<Namespace.Person>" %>
<h2>Some person</h2>
<div>Name: <%: Html.TextBoxFor(x => x.Name)%></div>
<div>Email address: <%: Html.TextBoxFor(x => x.Email)%></div>
<div>Date of birth: <%: Html.TextBoxFor(x => x.DateOfBirth)%></div>

then you can render a correctly indexed series of editors with a single line:

<%: Html.EditorFor(x => x.People) %>

Using Nonsequential Indexes

You've just seen how to use sequential, zero-based indexes (i.e., 0, 1, 2, etc.) to define collection items. A more flexible option is to use arbitrary string keys to define collection items. This can be beneficial if you might dynamically add or remove groups of controls using JavaScript on the client, and don't want to worry about keeping the indexes sequential.[75]

To use this option, each collection item needs to declare a special extra value called index that specifies the arbitrary string key you've chosen. For example, you could rewrite the previous example's view markup as follows:

<% using(Html.BeginForm()) { %>
    <h2>First person</h2>
    <input type="hidden" name="people.index" value="someKey" />
    <div>Name: <%: Html.TextBox("people[someKey].Name")%></div>
    <div>Email address: <%: Html.TextBox("people[someKey].Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[someKey].DateOfBirth")%></div>

    <h2>Second person</h2>
    <input type="hidden" name="people.index" value="anotherKey" />
<div>Name: <%: Html.TextBox("people[anotherKey].Name")%></div>
    <div>Email address: <%: Html.TextBox("people[anotherKey].Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[anotherKey].DateOfBirth")%></div>

    ...
    <input type="submit" />
<% } %>

There are multiple hidden fields called people.index, so ASP.NET will receive all their values combined into a single array. DefaultModelBinder will then use this as a hint for what indexes it should expect when binding to a collection called people.

Model-Binding to a Dictionary

If for some reason you'd like your action method to receive a dictionary rather than an array or a list, then you have to follow a modified naming convention that's more explicit about keys and values—for example:

<% using(Html.BeginForm()) { %>
    <h2>First person</h2>
    <input type="hidden" name="people[0].key" value="firstKey" />
    <div>Name: <%: Html.TextBox("people[0].value.Name")%></div>
    <div>Email address: <%: Html.TextBox("people[0].value.Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[0].value.DateOfBirth")%></div>

    <h2>Second person</h2>
    <input type="hidden" name="people[1].key" value="secondKey" />
    <div>Name: <%: Html.TextBox("people[1].value.Name")%></div>
    <div>Email address: <%: Html.TextBox("people[1].value.Email")%></div>
    <div>Date of birth: <%: Html.TextBox("people[1].value.DateOfBirth")%></div>

    ...
    <input type="submit" />
<% } %>

When bound to a Dictionary<string, Person> or IDictionary<string, Person>, this form data will yield two entries, under the keys firstKey and secondKey, respectively. You could receive the data as follows:

public ActionResult RegisterPersons(IDictionary<string, Person> people)
{
    // ...
}

Creating a Custom Value Provider

If you want to supply extra data items to the model binding system, you can do so by creating your own value provider. This technique is relevant if your application must obtain request-specific values from HTTP headers, cookies, or elsewhere, and you'd like those values to be easily accessible as action method parameters, just like query string or form values.

To create a custom value provider, it isn't enough just to implement IValueProvider. You must also create a factory class (inherited from ValueProviderFactory) so the framework can create a separate instance of your value provider for each HTTP request. Here's an example of a value provider and its associated factory rolled into one.

public class CurrentTimeValueProviderFactory : ValueProviderFactory
{
   public override IValueProvider GetValueProvider(ControllerContext ctx) {
      return new CurrentTimeValueProvider();
   }

   private class CurrentTimeValueProvider : IValueProvider
   {
     public bool ContainsPrefix(string prefix) {
        // Claim only to contain a single value called "currentTime"
        return "currentTime".Equals(prefix, StringComparison.OrdinalIgnoreCase);
     }

     public ValueProviderResult GetValue(string key)
     {
        return ContainsPrefix(key)
          ? new ValueProviderResult(DateTime.Now, null, CultureInfo.CurrentCulture)
          : null;
     }
  }
}

Whenever ASP.NET MVC asks this value provider for an item called currentTime, the value provider will return DateTime.Now. This allows your action methods to receive the current time simply by declaring a parameter called currentTime—for example:

public ActionResult Clock(DateTime currentTime)
{
    return Content("The time is " + currentTime.ToLongTimeString());
}

This is beneficial for unit testing: a unit test could call Clock(), supplying any DateTime value to act as the current time, which might be important if you need to test for some behavior that only occurs on weekends. Even if you aren't unit testing your action methods, it still helps to simplify your controllers if you perform storage and retrieval of custom context objects (e.g., SportsStore's Cart objects that are held in Session) using a value provider, because it means that actions can receive these context objects without needing to know or care where they come from.

To make this work at runtime, however, you need to tell ASP.NET MVC to use your new value provider. Add a line similar to the following to Application_Start() in Global.asax.cs:

ValueProviderFactories.Factories.Add(new CurrentTimeValueProviderFactory());

Or, if you want your custom value provider to be at the top of the priority list (so that the framework will use its values in preference to those from Request.Form, Request.QueryString, and so on), register it as follows:

ValueProviderFactories.Factories.Insert(0, new CurrentTimeValueProviderFactory());

Creating a Custom Model Binder

You've learned about the rules and conventions that DefaultModelBinder uses to populate arbitrary .NET types according to incoming data. Sometimes, though, you might want to bypass all that and set up a totally different way of using incoming data to populate a particular object type. To do this, implement the IModelBinder interface.

For example, if you want to receive an XDocument object populated using XML data from a hidden form field, you need a very different binding strategy. It wouldn't make sense to let DefaultModelBinder create a blank XDocument, and then try to bind each of its properties, such as FirstNode, LastNode, Parent, and so on. Instead, you'd want to call XDocument's Parse() method to interpret an incoming XML string. You could implement that behavior using the following class, which can be put anywhere in your ASP.NET MVC project:

public class XDocumentBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext)
    {
        // Get the raw attempted value from the value provider
        string key = bindingContext.ModelName;
        ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
        if ((val != null) && !string.IsNullOrEmpty(val.AttemptedValue)) {

            // Follow convention by stashing attempted value in ModelState
            bindingContext.ModelState.SetModelValue(key, val);

            // Try to parse incoming data
            string incomingString = ((string[])val.RawValue)[0];
            XDocument parsedXml;
            try {
                parsedXml = XDocument.Parse(incomingString);
            }
            catch (XmlException) {
                bindingContext.ModelState.AddModelError(key, "Not valid XML");
                return null;
            }

            // Update any existing model, or just return the parsed XML
            var existingModel = (XDocument)bindingContext.Model;
            if (existingModel != null) {
                if (existingModel.Root != null)
                    existingModel.Root.ReplaceWith(parsedXml.Root);
                else
                    existingModel.Add(parsedXml.Root);
                return existingModel;
            }
            else
                return parsedXml;
        }

        // No value was found in the request
        return null;
    }
}

This isn't as complex as it initially appears. All that a custom binder needs to do is accept a ModelBindingContext, which provides both the ModelName(the name of the parameter or prefix being bound) and a ValueProvider from which you can receive incoming data. The binder should ask the value provider for the raw incoming data, and can then attempt to parse the data. If the binding context provides an existing model object, then you should update that instance; otherwise, return a new instance.

Note

You might be wondering how custom value providers differ from custom model binders. A value provider can only provide incoming objects with particular string keys; it doesn't know what .NET type or object graph the model binder is trying to construct. A model binder is more complicated to implement, but it hooks into the process at a lower level and gives you more control. The preceding XDocument example needs this extra control—a mere value provider wouldn't know whether you wanted to receive a certain incoming value as a parsed XDocument instance or just as a string.

Configuring Which Model Binders Are Used

The MVC Framework won't use your new custom model binder unless you tell it to do so. If you own the source code to XDocument, you could associate your binder with the XDocument type by applying an attribute as follows:

[ModelBinder(typeof(XDocumentBinder))]
public class XDocument
{
    // ...
}

This attribute tells the MVC Framework that whenever it needs to bind an XDocument, it should use your custom binder class, XDocumentBinder. However, you probably can't change the source code to XDocument, so you need to use one of the following two alternative configuration mechanisms instead.

The first option is to register your binder with ModelBinders.Binders. You only need to do this once, during application initialization. For example, in Global.asax.cs, add the following:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.Add(typeof(XDocument), new XDocumentBinder());
}

The second option is to specify which model binder to use on a case-by-case basis. When binding action method parameters, you can use [ModelBinder], as follows:

public ActionResult MyAction([ModelBinder(typeof(XDocumentBinder))] XDocument xml)
{
    // ...
}

Unfortunately, if you're invoking model binding explicitly, it's somewhat messier to specify a particular model binder, because for some reason UpdateModel() has no overload to let you do so. Here's a utility method that you might want to add to your controller:

private void UpdateModelWithCustomBinder<TModel>(TModel model, string prefix,
                    IModelBinder binder, string include, string exclude)
{
    var modelType = typeof(TModel);
    var bindAttribute = new BindAttribute { Include = include, Exclude = exclude };
    var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model,
                                                                     modelType);
    var bindingContext = new ModelBindingContext
    {
        ModelMetadata = metadata,
        ModelName = prefix,
        ModelState = ModelState,
        ValueProvider = ValueProvider,
        PropertyFilter = bindAttribute.IsPropertyAllowed
    };
    binder.BindModel(ControllerContext, bindingContext);
    if (!ModelState.IsValid)
        throw new InvalidOperationException("Error binding " + modelType.FullName);
}

With this, you can now easily invoke your custom binder, as follows:

public ActionResult MyAction()
{
    var doc = new XDocument();
    UpdateModelWithCustomBinder(doc, "xml", new XDocumentBinder(), null, null);

    // ...
}

So, there are several ways of nominating a model binder. How does the framework resolve conflicting settings? It selects model binders according to the following priority order:

  1. The binder explicitly specified for this binding occasion (e.g., if you're using a [ModelBinder] attribute on an action method parameter).

  2. The binder registered in ModelBinders.Binders for the target type.

  3. The binder assigned using a [ModelBinder] attribute on the target type itself.

  4. The default model binder. Usually, this is DefaultModelBinder, but you can change that by assigning an IModelBinder instance to ModelBinders.Binders.DefaultBinder. Configure this during application initialization—for example, in Global.asax.cs's Application_Start() method.

Tip

Specifying a model binder on a case-by-case basis (i.e., option 1) makes most sense when you're more concerned about the incoming data format than about what .NET type it needs to map to. For example, you might sometimes receive data in JSON format, in which case it makes sense to create a JSON binder that can construct .NET objects of arbitrary type. You wouldn't register that binder globally for any particular model type, but would just nominate it for certain binding occasions.

Using Model Binding to Receive File Uploads

Back in Table 12-7, you saw that one of ASP.NET MVC's built-in value providers is HttpFileCollectionValueProvider. This gives you an easy way to receive uploaded files. All you have to do is accept a method parameter of type HttpPostedFileBase, and ASP.NET MVC will populate it (where possible) with data corresponding to an uploaded file.[76]

For example, to let the user upload a file, add to one of your views a <form> like this:

<form action="<%: Url.Action("UploadPhoto") %>"
      method="post"
      enctype="multipart/form-data">
    Upload a photo: <input type="file" name="photo" />
<input type="submit" />
</form>

You can then retrieve and work with the uploaded file in the action method:

public ActionResult UploadPhoto(HttpPostedFileBase photo)
{
    // Save the file to disk on the server
    string filename = // ... pick a filename
    photo.SaveAs(filename);

    // ... or work with the data directly
    byte[] uploadedBytes = new byte[photo.ContentLength];
    photo.InputStream.Read(uploadedBytes, 0, photo.ContentLength);
    // Now do something with uploadedBytes
}

Note

The previous example showed a <form> tag with an attribute you may find unfamiliar: enctype="multipart/form-data". This is necessary for a successful upload! Unless the form has this attribute, the browser won't actually upload the file—it will just send the name of the file instead, and the Request.Files collection will be empty. (This is how browsers work; ASP.NET MVC can't do anything about it.) Similarly, the form must be submitted as a POST request (i.e., method="post"); otherwise, it will contain no files.

In this example, I chose to render the <form> tag by writing it out as literal HTML. Alternatively, you can generate a <form> tag with an enctype attribute by using Html.BeginForm(), but only by using the four-parameter overload that takes a parameter called htmlAttributes. Personally, I think literal HTML is more readable than sending so many parameters to Html.BeginForm().

Validation

What is validation? There's a whole range of ways you can think about it, including

  • Making demands about the presence or format of data that users may enter into a UI

  • Determining whether a certain .NET object is in a state that you consider valid

  • Applying business rules to allow or prevent certain operations being carried out against your domain model

ASP.NET MVC's built-in validation support focuses mainly on the first and second requirements. As part of the model binding process, the framework will test whether the bound object complies with your rules. If it doesn't, you can use built-in HTML helpers to display validation error messages. You can even try to avoid the validation errors in the first place by using your rules to generate a client-side validation script that will restrict what data users may enter.

At the end of this chapter, we'll consider the third requirement: integrating this system with business rules that live in your domain model layer. This goes beyond the simple notion of validating an object's state and deals with validating an operation in a certain context. For example, users may be allowed to edit a document's title, but not if the document has already been published, unless the user is an administrator. Your domain model can robustly protect itself, and you can use the same validation HTML helpers to provide UI feedback.

Registering and Displaying Validation Errors

Before you start thinking about defining validation rules declaratively using attributes or custom validation providers, it's important to understand how you can implement validation logic directly inside an action method and then display error messages to users.

As you learned earlier in this chapter, the MVC Framework uses ModelState as a place to store information about what's happening with a model object during the current request. The model binding system uses ModelState to store both incoming attempted values and details of any binding errors. You can also manually register errors in ModelState. Altogether, this is how to communicate error information to views, and is also how input controls can recover their previous state after a validation or model binding failure.

Here's an example. You're creating a controller called BookingController, which lets users book appointments. Appointments are modeled as follows:

public class Appointment
{
    public string ClientName { get; set; }

    [DataType(DataType.Date)]
    public DateTime AppointmentDate { get; set; }
}

To place a booking, users first visit BookingController's MakeBooking action:

public class BookingController : Controller
{
    public ViewResult MakeBooking()
    {
        var initialState = new Appointment {
            AppointmentDate = DateTime.Now.Date
};
        return View(initialState);
    }
}

This action does nothing more than render its default view, MakeBooking.aspx (strongly typed with model type Appointment), which includes the following form:

<h1>Book an appointment</h1>

<% using(Html.BeginForm()) { %>
    <p>
        Your name: <%: Html.EditorFor(x => x.ClientName) %>
    </p>
    <p>
        Appointment date:
        <%:Html.EditorFor(x => x.AppointmentDate)%>
    </p>
    <p>
        <%: Html.CheckBox("acceptsTerms") %>
        <label for="acceptsTerms">I accept the Terms of Booking</label>
    </p>

    <input type="submit" value="Place booking" />
<% } %>

This action will now render as shown in Figure 12-8.

Initial screen rendered by the MakeBooking action

Figure 12-8. Initial screen rendered by the MakeBooking action

Since the view template generates a form tag by calling Html.BeginForm() without specifying an action name parameter, the form posts to the same URL that generated it. In other words, to handle the form post, you need to add another action method called MakeBooking(), except this one should handle POST requests. Here's how it can detect and register validation errors:

[HttpPost]
public ActionResult MakeBooking(Appointment appt, bool acceptsTerms)
{
    if (string.IsNullOrEmpty(appt.ClientName))
        ModelState.AddModelError("ClientName", "Please enter your name");

    if (ModelState.IsValidField("AppointmentDate"))
    {
        // Parsed the DateTime value. But is it acceptable under our app's rules?
        if (appt.AppointmentDate < DateTime.Now.Date)
            ModelState.AddModelError("AppointmentDate", "The date has passed");
        else if ((appt.AppointmentDate - DateTime.Now).TotalDays > 7)
            ModelState.AddModelError("AppointmentDate",
                                     "You can't book more than a week in advance");
    }

    if (!acceptsTerms)
        ModelState.AddModelError("acceptsTerms", "You must accept the terms");

    if (ModelState.IsValid)
    {
        // To do: Actually save the appointment to the database or whatever
        return View("Completed", appt);
    }
    else
        return View(); // Re-renders the same view so the user can fix the errors
}

The preceding code won't win any awards for elegance or clarity. I'll soon describe a tidier way of doing this, but for now I'm just trying to demonstrate the most basic way of registering validation errors.

Note

I've included DateTime in this example so that you can see that it's a tricky character to deal with. It's a value type, so the model binder will register the absence of incoming data as an error, just as it registers an unparsable date string as an error. You can test whether the incoming value was successfully parsed by calling ModelState.IsValidField(...)—if it wasn't, there's no point applying any other validation logic to that field.

This action method receives incoming form data as parameters via model binding. It then enforces certain validation rules in the most obvious and flexible way possible—plain C# code—and for each rule violation, it records an error in ModelState, giving the name of the input control to which the error relates. Finally, it uses ModelState.IsValid (which checks whether any errors were registered, either by you or by the model binder) to decide whether to accept the booking or redisplay the same data entry screen.

It's a very simple validation pattern, and it works just fine. However, if the user enters invalid data right now, they won't see any error messages, because the view doesn't contain instructions to display them.

Using the Built-In Validation HTML Helpers

The easiest way to tell your view to render error messages is as follows. Just place a call to Html.ValidationSummary() somewhere inside the view—for example:

<h1>Book an appointment</h1>
<%: Html.ValidationSummary() %>
... all else unchanged ...

This helper simply produces a bulleted list of errors recorded in ModelState. If you submit a blank form, you'll now get the output shown in Figure 12-9.

Validation messages rendered by Html.ValidationSummary

Figure 12-9. Validation messages rendered by Html.ValidationSummary

Tip

You can also pass to Html.ValidationSummary() a parameter called message. This string will be rendered immediately above the bulleted list if there is at least one registered error. For example, you could display the heading "Please amend your submission, and then resubmit it."

There are two things to notice about this screen:

  • Where did the "The AppointmentDate field is required" message come from? That's not in my controller! Yes, when the framework prepares a ModelMetadata instance to describe a property that can't hold null(such as DateTime, a value type), it automatically sets the metadata's IsRequired flag to true. Then, the built-in default validation provider enforces this rule. If you don't like this, change the property type to DateTime?, or explicitly add your own required field validator for that property giving an alternative error message, as discussed shortly.

  • Some of the input controls are highlighted with a shaded background to indicate their invalidity. The framework's built-in HTML helpers for input controls are smart enough to notice when they correspond to a ModelState entry that has errors, and will give themselves relevant CSS classes including input-validation-error and validation-summary-errors. Whenever you use Visual Studio to create a new ASP.NET MVC project, it gives you a basic stylesheet at /Content/Site.css that declares all of these validation CSS classes.

Controlling Where Validation Messages Appear

Alternatively, you can choose not to use Html.ValidationSummary(), and instead to use a series of Html.ValidationMessage() or Html.ValidationMessageFor() helpers to place specific potential error messages at different positions in your view. For example, update MakeBooking.aspx as follows:

<% using(Html.BeginForm()) { %>
    <p>
        Your name: <%: Html.EditorFor(x => x.ClientName) %>
        <%: Html.ValidationMessageFor(x => x.ClientName) %>
    </p>
    <p>
        Appointment date:
        <%: Html.EditorFor(x => x.AppointmentDate)%>
        <%: Html.ValidationMessageFor(x => x.AppointmentDate) %>
    </p>
    <p>
        <%: Html.CheckBox("acceptsTerms") %>
        <label for="acceptsTerms">I accept the Terms of Booking</label>
        <%: Html.ValidationMessage("acceptsTerms") %>
    </p>

    <input type="submit" value="Place booking" />
<% } %>

Now, a blank form submission would produce the display shown in Figure 12-10.

Validation messages rendered by the validation message helpers

Figure 12-10. Validation messages rendered by the validation message helpers

Distinguishing Property-Level Errors from Model-Level Errors

Some validation error messages may relate to specific properties, while others may relate to the entire model object and not any single specific property. You've already seen how to register property-level errors by passing the property name as a parameter (e.g., ModelState.AddModelError("ClientName", message)). You can register model-level errors by passing an empty string for the key parameter—for example:

bool isSaturday = appt.AppointmentDate.DayOfWeek == DayOfWeek.Saturday;
if (appt.ClientName == "Steve" && isSaturday)
    ModelState.AddModelError("" /* key */, "Steve can't book on Saturdays");

By default, Html.ValidationSummary() shows both model- and property-level errors. But if you're rendering property-level errors in other places using Html.ValidationMessage() or Html.ValidationMessageFor(), you probably don't want property-level errors to be duplicated in the validation summary.

To fix this, you can instruct Html.ValidationSummary() to display only model-level errors (i.e., those registered with an empty key) so that the user sees no duplication. Just pass true for its excludePropertyErrors parameter—that is, call Html.ValidationSummary(true). You can see example output in Figure 12-11.

When instructed to exclude property-level errors, the validation summary will not duplicate property-level messages that may be displayed elsewhere.

Figure 12-11. When instructed to exclude property-level errors, the validation summary will not duplicate property-level messages that may be displayed elsewhere.

How the Framework Retains State After a Validation Failure

To create the preceding screenshot (Figure 12-11), I entered the values shown and clicked "Place booking." When the form reappeared with validation error messages, the data I previously entered (in this case a name and a date) was still present in the form fields.

ASP.NET Web Forms achieves a kind of statefulness using its ViewState mechanism, but there's no such mechanism in ASP.NET MVC. So how was the state retained?

Once again, it's because of a convention. The convention is that input controls should populate themselves using data taken from the following locations, in this order of priority:

  1. Previously attempted value recorded in ModelState["name"].Value.AttemptedValue

  2. Explicitly provided value (e.g., Html.TextBox("name", "Some value") or Html.TextBoxFor(x => x.SomeProperty))

  3. ViewData, by calling ViewData.Eval("name") (so ViewData["name"] takes precedence over ViewData.Model.name)

Since model binders record all attempted values in ModelState, regardless of validity, the built-in HTML helpers naturally redisplay attempted values after a validation or model binding failure. And because this takes top priority, even overriding explicitly provided values, then any explicitly provided values are really just initial control values.

Performing Validation As Part of Model Binding

If you think about how the preceding appointment booking example works, you'll notice that there are two distinct phases of validation:

  • First, DefaultModelBinder enforces some basic data formatting rules as it parses incoming values and tries to assign them to the model object. For example, if it can't parse the incoming appt.AppointmentDate value as a DateTime, then DefaultModelBinder registers a validation error in ModelState.

  • Second, after model binding is completed, our MakeBooking() action method checks the bound values against custom business rules. If it detects any rule violations, it also registers those as errors in ModelState.

You'll consider how to improve and simplify the second phase of validation shortly. But first, you'll learn how DefaultModelBinder does validation and how you can customize that process if you want.

There are five virtual methods on DefaultModelBinder relating to its efforts to validate incoming data. These are listed in Table 12-8.

Table 12-8. Overridable Validation Methods on DefaultModelBinder

Method

Description

Default Behavior

OnModelUpdating

This runs when DefaultModelBinder is about to update the values of all properties on a custom model object. It returns a bool value to specify whether binding should be allowed to proceed.

It does nothing—just returns true.

OnModelUpdated

This runs after DefaultModelBinder has tried to update the values of all properties on a custom model object.

It invokes all the ModelValidator instances associated with your model's metadata and registers any validation errors in ModelState.

OnPropertyValidating

This runs before each time DefaultModelBinder applies a value to a property on a custom model object. It returns a bool value to specify whether the value should be applied.

It does nothing—just returns true.

OnPropertyValidated

This runs after each time DefaultModelBinder has tried to apply a value to a property on a custom model object.

It does nothing.

SetProperty

This is the method that DefaultModelBinder calls to apply a value to a property on a custom model object.

If the property cannot hold null values and there was no parsable value to apply, then it registers an error in ModelState(taking the error message from an associated "required" validator if there is one). Also, if the value could not be parsed, or if applying it causes a setter exception, this will be registered as an error in ModelState.

If you want to implement a different kind of validation during data binding, you can create a subclass of DefaultModelBinder and override the relevant methods listed in the preceding table. Then hook your custom binder into the MVC Framework by adding the following line to your Global.asax.cs file:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.DefaultBinder = new MyModelBinder();
}

However, it's rarely necessary to subclass DefaultModelBinder, especially not as a way of implementing custom validation rules, because the framework provides a whole system for defining validation rules using either Data Annotations attributes or a custom provider. DefaultModelBinder will invoke your rules as part of its OnModelUpdated() behavior.

Specifying Validation Rules

Not surprisingly, ASP.NET MVC's approach to defining validation rules follows the usual provider pattern so you can extend or replace it if you wish. Figure 12-12 illustrates how arbitrary validation rule sources are mapped to standard ModelValidator instances.

The validation extensibility mechanism

Figure 12-12. The validation extensibility mechanism

When any component (such as DefaultModelBinder) wishes to know the validation rules associated with a particular ModelMetadata instance, it calls ModelMetadata's virtual GetValidators() method, which in turn asks all your registered validation providers (such as DataAnnotationsModelValidationFactory) to return a set of ModelValidator instances related to that model item.

We'll start by looking at the framework's built-in validation providers and then move on to see how you could create one of your own.

Using Data Annotations Validation Attributes

By default, ASP.NET MVC applications are configured to use DataAnnotationsModelValidationFactory, which recognizes the Data Annotations attributes listed in Table 12-9.

Table 12-9. Data Annotations Validation Attributes Recognized by ASP.NET MVC

Attribute

Meaning

[Range]

A numeric value (or any property type that implement IComparable) must not lie beyond the specified minimum and maximum values. To specify a boundary only on one side, use a MinValue or MaxValue constant—for example, [Range(int.MinValue, 50)].

[RegularExpression]

A string value must match the specified regular expression pattern. Note that your pattern has to match the entire user-supplied value, not just a substring within it. By default, it matches case sensitively, but you can make it case insensitive by applying the (?i) modifier—that is, [RegularExpression("(?i)mypattern")].

[Required]

The value must not be empty or be a string consisting only of spaces. If you want to treat whitespace as valid, use [Required(AllowEmptyStrings = true)].

[StringLength]

A string value must not be longer than the specified maximum length. In .NET 4, you can also specify a minimum length.

Warning

Even though [DataType] looks like a validation attribute along with the others in Data Annotations, ASP.NET MVC does not treat it as one, so don't expect [DataType(DataType.EmailAddress)] to validate for legal e-mail addresses! [DataType] is an anomaly; even though it inherits from System.ComponentModel.DataAnnotations.ValidationAttribute, its IsValid() method is hard-coded to return true regardless of the property's value. Microsoft has explained that [DataType] is only meant to serve as a hint for formatting data in a scaffolded UI, though it still seems strange that it inherits from ValidationAttribute.

When you use any of the Data Annotations validation attributes, you can supply a custom error message as a parameter in either of the following two ways:

[AttributeName(ErrorMessage = "Your custom error message")]

or

[AttributeName(ErrorMessageResourceName = "YourResourceEntryName",
               ErrorMessageResourceType = typeof(YourResources))]

The second option is intended to work with RESX localization files—you need to give the .NET type name corresponding to the RESX file, and at runtime the framework will extract the named resource string according to the active thread culture. You'll learn more about working with RESX files and localization in Chapter 17.

Continuing the previous example, you could apply these attributes to the Appointment model class as follows:

public class Appointment
{
    [Required(ErrorMessage = "Please enter your name")] [StringLength(50)]
    public string ClientName { get; set; }

    [DataType(DataType.Date)] [Required(ErrorMessage = "Please choose a date")]
    public DateTime AppointmentDate { get; set; }
}

These rules will now be applied during model binding, and any violations will be registered in ModelState automatically, so it's no longer necessary for your controller to check whether ClientName was provided. Of course, you can still implement further validation logic directly inside your controller, which in this example is necessary to validate that the booking date falls within the next week, and that the Terms of Booking check box was checked.

Tip

It would make sense to add a bool property called AcceptsTerms to the Appointment class and then apply a [Required] validator to it. That way, you wouldn't need any custom logic for it in your controller. The reason I haven't done that in this example is to illustrate that you can write code to validate any incoming data, whether or not it maps to a model property.

Creating a Custom Data Annotations Validation Attribute

It's very easy to create your own validation attribute that DataAnnotationsModelValidationFactory can recognize. Just inherit from the Data Annotations ValidationAttribute base class. Here's a simple example:

public class ValidEmailAddressAttribute : ValidationAttribute
{
    public ValidEmailAddressAttribute()
    {
        // Default message unless declared on the attribute
        ErrorMessage = "{0} must be a valid email address.";
    }

    public override bool IsValid(object value)
    {
        // You might want to enhance this logic...
        string stringValue = value as string;
        if (stringValue != null)
            return stringValue.Contains("@");
        return true;
    }
}

It's conventional for IsValid() to return true if the supplied value is empty. Otherwise, you're implicitly making the property required even if there is no [Required] attribute associated with it.

Using the IDataErrorInfo Interface

As well as DataAnnotationsModelValidationFactory, the framework also includes DataErrorInfoModelValidatorProvider. This provides a more awkward and less powerful way of implementing custom validation logic, and is mainly intended for backward compatibility with ASP.NET MVC 1, where DefaultModelBinder was hard-coded to recognize an interface called IDataErrorInfo.

To use this, make your model class implement the IDataErrorInfo interface. This requires you to implement two methods—one to return property-level errors, and another to return object-level errors—for example:

public class Appointment : IDataErrorInfo
{
    public string ClientName { get; set; }
    public DateTime AppointmentDate { get; set; }

    public string this[string columnName]
    {
        get {
            if (columnName == "ClientName") {
                if (string.IsNullOrEmpty(ClientName))
                    return "Please enter a name.";
            }
            if (columnName == "AppointmentDate")
            {
                if (AppointmentDate < DateTime.Now.Date)
                    return "Bookings cannot be placed in the past";
            }
            return null; // No property-level errors
        }
    }

    public string Error
    {
        get {
            if (ClientName == "Steve"
                && AppointmentDate.DayOfWeek == DayOfWeek.Saturday)
                return "Steve can't book on Saturdays.";
            return null; // No object-level errors
        }
    }
}

Now you can simplify the MakeBooking action as follows:

[HttpPost]
public ActionResult MakeBooking(Appointment appt, bool acceptsTerms)
{
    if (!acceptsTerms)
        ModelState.AddModelError("acceptsTerms", "You must accept the terms");

    if (ModelState.IsValid) {
        // To do: Actually save the appointment to the database or whatever
return View("Completed", appt);
    }
    else
        return View(); // Re-renders the same view so the user can fix the errors
}

DataErrorInfoModelValidatorProvider will call your IDataErrorInfo methods and populate ModelState as part of model binding. You can still add extra validation logic, as in this example with the Accepts Terms check box, directly inside your action method.

This whole technique is much less useful than using Data Annotations attributes or a custom validation provider for several reasons:

  • It provides no easy means of reusing validation logic between different model classes.

  • It provides no means of reporting multiple errors relating to a single property, or multiple errors relating to the whole model object, other than concatenating all the messages into a single string.

  • It provides no means of generating client-side validation scripts.

It's good that ASP.NET MVC 2 supports IDataErrorInfo as a matter of backward compatibility, but most developers will not want to use it now that better alternatives exist.

Creating a Custom Validation Provider

If you want to go in a different direction from Data Annotations attributes, you can create a custom validation provider by inheriting a class from either of the following base classes:

  • ModelValidatorProvider: the abstract base class for all validator providers

  • AssociatedValidatorProvider: usually a better choice of base class if your validation rules are expressed mainly as .NET attributes, because it deals with the tricky business of detecting custom attributes, including transparently fetching them from "buddy" classes referenced by [MetadataType] attributes

Either way, you must override a method called GetValidators() and return a set of ModelValidator instances. This lets you hook into the validation system at a lower level than a Data Annotations ValidationAttribute, so you get more control over what happens.

Why would you want to do this? As an example, you might want to validate that two model properties must be equal. The ASP.NET MVC 2 Web Application project template includes its own custom validation attribute, PropertiesMustMatchAttribute, which you can apply to a model class and specify the names of the two properties that must match. But what if you want to apply a validation attribute to a property (not to the whole model class) and say that its value must match the value of another property?

You can't easily do this by inheriting from ValidationAttribute, because Data Annotations is mainly intended for validating objects in isolation, and its API doesn't provide any way for you to access sibling properties. In situations like this, you'll need to step away from Data Annotations and implement a totally separate custom validation provider.

To get started, define the following custom attribute:

public class EqualToPropertyAttribute : Attribute
{
    public readonly string CompareProperty;

    public EqualToPropertyAttribute(string compareProperty)
{
        CompareProperty = compareProperty;
    }
}

Using this attribute, you could declare that two password fields have to match.

public class UserRegistrationViewModel
{
    // ... other properties ...

    [Required] [DataType(DataType.Password)]
    public string Password { get; set; }

    [Required] [DataType(DataType.Password)] [EqualToProperty("Password")]
    public string ConfirmPassword { get; set; }
}

Next, you need to define a custom validator provider that can detect [EqualToProperty] attributes and convert them into instances of a ModelValidator subclass. Here's one such class that returns instances of a new class, EqualToPropertyValidator, which we'll define in a moment:

public class MyValidatorProvider : AssociatedValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context,
        IEnumerable<Attribute> attributes)
    {
        foreach (var attrib in attributes.OfType<EqualToPropertyAttribute>())
            yield return new EqualToPropertyValidator(metadata, context,
                                                      attrib.CompareProperty);
    }
}

You need to tell ASP.NET MVC to use your new validator provider by registering it in the static ModelValidatorProviders.Providers collection. For example, update Application_Start() in Global.asax.cs as follows:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ModelValidatorProviders.Providers.Add(new MyValidatorProvider());
}

Of course, before any of this will compile, you'll also need to implement the EqualToPropertyValidator class previously referenced by the validator provider. This is where all the real validation logic lives.

public class EqualToPropertyValidator : ModelValidator
{
   private readonly string compareProperty;

   public EqualToPropertyValidator(ModelMetadata metadata,
       ControllerContext context, string compareProperty) : base(metadata, context)
   {
this.compareProperty = compareProperty;
   }

   public override IEnumerable<ModelValidationResult> Validate(object container)
   {
      if (Metadata.Model == null)
          yield break;

      var propertyInfo = container.GetType().GetProperty(compareProperty);
      if (propertyInfo == null)
          throw new InvalidOperationException("Unknown property:"+compareProperty);
      var valueToCompare = propertyInfo.GetValue(container, null);

      if (!Metadata.Model.Equals(valueToCompare))
          yield return new ModelValidationResult {
              Message = "This value must equal the value of " + compareProperty
          };
   }
}

As you can see, custom ModelValidator instances get access to the property's complete set of ModelMetadata information, as well as the container object of which the property is part. Your validator provider can supply any collection of ModelMetadata instances, so you're not limited just to using .NET attributes—you can obtain your configuration from any source.

Invoking Validation Manually

Whenever you use model binding to populate a model object—either by receiving it as an action method parameter, or by calling UpdateModel() or TryUpdateModel() manually—then DefaultModelBinder will automatically run the validators associated with all model objects that it has updated (i.e., ones where it has set a value on at least one property).

If you update a model object in any other way, its validators will not be run unless you explicitly tell the framework to run them. For example, you might sometimes manually update model properties within an action method as shown here:

[HttpPost]
public ActionResult MakeBooking(string clientName, DateTime? appointmentDate,
                                bool acceptsTerms)
{
    var appt = new Appointment {
        ClientName = clientName,
        AppointmentDate = appointmentDate.GetValueOrDefault()
    };

    if (!acceptsTerms)
        ModelState.AddModelError("acceptsTerms", "You must accept the terms");

    if (ModelState.IsValid) {
        // To do: Actually save the appointment to the database or whatever
        return View("Completed", appt);
    }
    else
        return View(); // Re-renders the same view so the user can fix the errors
}

Now, the custom validation logic for acceptsTerms is still enforced, but any validators associated with the Appointment class or its properties will not be run, so they will never register any errors in ModelState.

The Controller base class exposes two methods that will run the validators associated with an arbitrary object at any time:

  • ValidateModel() runs the validators, registers any errors in ModelState, and finishes by throwing an InvalidOperationException if ModelState contains at least one error.

  • TryValidateModel() does the same, except instead of throwing an exception, it returns a bool value to signal the result. The return value is actually just the value of ModelState.IsValid.

So, you could update the previous code sample by calling TryValidateModel() immediately after populating the Appointment instance.

var appt = new Appointment {
    ClientName = clientName,
    AppointmentDate = appointmentDate.GetValueOrDefault()
};
TryValidateModel(appt);

Even though we're ignoring the return value from TryUpdateModel(), it will register any errors in ModelState, so the subsequent code that checks ModelState.IsValid will work as expected.

Warning

It's difficult to validate value type properties when you call ValidateModel() or TryValidateModel() manually. For example, since you can't assign a null value for a DateTime property, it will certainly hold some date value, so it's meaningless to say that the property is required. To work around this, you'd need to change the AppointmentDate property to be of the nullable type DateTime?, and then remove GetValueOrDefault() from the assignment. DefaultModelBinder doesn't have this problem because it knows whether it has just applied a value from the request to each property.

Using Client-Side Validation

In web applications, most people expect to see validation feedback immediately, before submitting anything to the server. This is known as client-side validation, usually implemented using JavaScript. Pure server-side validation is robust, but doesn't yield a great end-user experience unless accompanied by client-side validation.

ASP.NET MVC has a system for generating client-side validation scripts directly from the ModelValidator instances associated with your model object's metadata. It's pretty easy to use—if you're using standard Data Annotation attributes, then in most cases it will only take one extra line of code to enable client-side validation.

Continuing the previous appointment booking example, in the MakeBooking.aspx view, call Html.EnableClientValidation() before rendering the form:

<% Html.EnableClientValidation(); %>
<% using(Html.BeginForm()) { %>
    ... rest as before ...

Note that Html.EnableClientValidation() returns void, so you must not try to emit any output from it by using <%: ... %> or <%= ... %>. If you do, you'll get a compilation error.

Also, make sure that somewhere in your view or its master page you've referenced the following two scripts, which Visual Studio automatically provides when you create any new ASP.NET MVC 2 project. A good place to reference them is at the bottom of a master page, right before the closing </body> tag.

<script type="text/javascript"
        src="<%: Url.Content("~/Scripts/MicrosoftAjax.js") %>"></script>
<script type="text/javascript"
        src="<%: Url.Content("~/Scripts/MicrosoftMvcValidation.js") %>"></script>

Now, if you reload the appointment booking form in your browser, you should find that the [Required] and [StringLength] rules will be enforced on the client using JavaScript. Validation messages will appear and disappear dynamically, and until the user supplies acceptable values to satisfy these rules, the form cannot be submitted.

Warning

I expect most readers will find this obvious, but it's so important I still have to point it out. Enabling client-side validation is not a substitute for enforcing validation on the server! You still need to check ModelState.IsValid(or use some other mechanism for ensuring validity), because client-side validation can easily be bypassed. Users can disable JavaScript in their browsers, or they can use some other tool to send an arbitrary HTTP POST request to your server. See Chapter 15 for more details.

Using Client-Side Validation with a Validation Summary

The MicrosoftMvcValidation.js script is smart enough to notice if your form contains a validation summary rendered using Html.ValidationSummary(), and if it does, the script will dynamically show and hide messages in the summary list as needed. Here's how you could update the MakeBooking.aspx view to do client-side validation with a validation summary:

<% Html.EnableClientValidation(); %>
<% using(Html.BeginForm()) { %>
    <%: Html.ValidationSummary() %>
    <% Html.ValidateFor(x => x.ClientName); %>
    <% Html.ValidateFor(x => x.AppointmentDate); %>

    <p>Your name: <%: Html.EditorFor(x => x.ClientName) %></p>
    <p>Appointment date: <%: Html.EditorFor(x => x.AppointmentDate)%></p>
    <p>
        <%: Html.CheckBox("acceptsTerms") %>
        <label for="acceptsTerms">I accept the Terms of Booking</label>
    </p>

    <input type="submit" value="Place booking" />
<% } %>

This is different from the previous version of the view in two main ways:

  • Html.ValidationSummary() is now inside the form. It has to be—views can contain any number of forms, and any number of validation summaries. MicrosoftMvcValidation.js resolves the possible ambiguity by associating each form with the validation summary that it contains.

  • Instead of using Html.ValidationMessageFor() to display messages at specific locations, we're now using Html.ValidateFor(). This HTML helper doesn't emit any HTML; it just tells ASP.NET MVC to register client-side validation metadata in FormContext for the referenced model item. Without this, those fields wouldn't be validated on the client, and the validation summary would only be updated after the whole form was posted to the server.

Not surprisingly, the string-based equivalent of Html.ValidateFor() is called Html.Validate(). But even with this, we still can't validate the acceptsTerms check box on the client, because it doesn't correspond to any model property, so there's no metadata associated with it.

Dynamically Highlighting Valid and Invalid Fields

As you learned earlier in this chapter, ASP.NET MVC's built-in HTML helpers use certain CSS classes, such as input-validation-error, to highlight themselves when they correspond to validation errors in ModelState. To fit in with this convention, MicrosoftMvcValidation.js will dynamically add and remove these CSS classes on your input controls while the user is entering data into the form.

What's less well known is that MicrosoftMvcValidation.js also dynamically applies a further CSS class, input-validation-valid, to input elements once they've been detected as valid. This means you can highlight "good" values, reassuring the user that their data will be accepted. Validation doesn't always have to be negative!

For example, if you add the following rule to your CSS file:

.input-validation-valid { border: 1px solid green; background-color: #CCFFCC; }

then for any form that has client-side validation enabled, whenever the user types a valid value into a text box, that text box will turn green.

Allowing Specific Buttons to Bypass Validation

By default, client-side validation prevents the user from submitting a form while one or more validation errors are present. Normally that's exactly what you want. If for some reason you want a button to be able to submit a form regardless of whether it's displaying any validation errors, you can assign a value to a property called disableValidation on that button's DOM node.

For example, if you have a submit button defined as follows:

<input id="submitBooking" type="submit" value="Place booking" />

then the following JavaScript allows it to bypass client-side validation:

<script type="text/javascript">
    document.getElementById("submitBooking").disableValidation = true;
</script>

How Client-Side Validation Works

If you look at the HTML source code for your view once rendered in a browser, you'll notice that just after the closing </form> tag, the MVC Framework has emitted a JavaScript block that describes and applies your client-side validation rules:

...
</form><script type="text/javascript">
//<![CDATA[
     window.mvcClientValidationMetadata=[];
 }
 window.mvcClientValidationMetadata.push({"Fields":[{ "FieldName":"ClientName", ...
//]]>
</script>

To get an overview of how this information has reached the browser in JavaScript form, see Figure 12-13. We'll use this understanding in a moment when implementing client-side validation logic for a custom validation rule.

How client-side validation metadata is collected and emitted

Figure 12-13. How client-side validation metadata is collected and emitted

The ModelValidator base class exposes a virtual method, GetClientValidationRules(), that can return a set of ModelClientValidationRule instances. Each ModelClientValidationRule instance is a description of how that rule should be represented in the client-side JavaScript Object Notation (JSON) block. The framework's JavaScript libraries understand the JSON descriptions of standard Data Annotations validators such as [Required] and [StringLength], plus they let you register your own JavaScript functions to implement custom validation logic for other rules.

Note

ASP.NET MVC also automatically adds client-side validators for all properties of numeric types (i.e., byte, sbyte, short, ushort, int, uint, long, ulong, float, double, and decimal). This is to make client-side validation more consistent with server-side validation, which has no choice but to reject nonnumeric strings for numeric data types (because there's no way the model binder could put a nonnumeric value into a .NET int property).

Implementing Custom Client-Side Validation Logic

Currently, if you use the [EqualToProperty] custom validation rule (which we created earlier in the chapter) in a form with client-side validation enabled, rules from Data Annotations may be validated on the client, but [EqualToProperty] will not—it will only be validated on the server. It's hardly surprising—ASP.NET MVC can't automatically translate arbitrary server-side .NET code into JavaScript. Let's see how to use ModelValidator's GetClientValidationRules(), plus some JavaScript code, to run [EqualToProperty] on the client too.

First, update EqualToPropertyValidator by overriding its GetClientValidationRules() method. You can return any ValidationType and ValidationParameters values—these will be made available on the client as part of the rule's JSON description.

public class EqualToPropertyValidator : ModelValidator
{
   // ... rest as before

  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
  {
     var clientValidationRule = new ModelClientValidationRule {
         ValidationType = "EqualToProperty",
         ErrorMessage = "This value must equal the value of " + compareProperty
     };
     clientValidationRule.ValidationParameters["compareTo"] = compareProperty;
     yield return clientValidationRule;
  }
}

Next, add some JavaScript code directly after wherever you've referenced MicrosoftMvcValidation.js, adding a new custom validator to the client-side validation registry. Note that your new client-side validator must be named to match whatever ValidationType you declare in your ModelClientValidationRule, which in this case is EqualToProperty.

<script type="text/javascript">
    Sys.Mvc.ValidatorRegistry.validators.EqualToProperty = function (rule) {
        // Prepare by extracting any parameters sent from the server
        var compareProperty = rule.ValidationParameters.compareTo;

        // Return a function that tests a value for validity
return function (value, context) {
            // Find the comparison element by working out what its name must be
            var thisElement = context.fieldContext.elements[0];
            var compareName = thisElement.name.replace(/[^.]*$/, compareProperty);
            var compareElement = document.getElementsByName(compareName)[0];

            // Check that their values match
            return value == compareElement.value;
        }
    };
</script>

This is an unusually complicated client-side validator, because it has to locate another element in the HTML DOM to compare against. If you only need to validate a single value in isolation, your client-side validator would be simpler:

<script type="text/javascript">
    Sys.Mvc.ValidatorRegistry.validators.MyValidationType= function (rule) {
        return function (value, context) {
            // Return true if 'value' is acceptable; otherwise false.
            // Alternatively, return a non-null string to show a custom message
        }
    };
</script>

When the framework calls your custom validation function, it passes two parameters: value, which of course is the value to be validated, and context, which has the properties listed in Table 12-10.

Table 12-10. Useful Properties Available on the Context Object Passed to a Validation Function

Property

Description

eventName

Takes one of three values: input, when the user is currently typing into the field; blur, when the user has just moved the focus away from the field; and submit, when the user has just asked for the form to be submitted. This lets you choose when to make a validation error message appear. For example, you could write if(context.eventName != 'submit') return true; to mean that your validation message should not appear until the user tries to submit the form. Note that, to avoid displaying error messages too early, input events don't fire until either a blur or a submit event has already fired at least once. Also note that, due to Internet Explorer quirks, the input event doesn't fire on Internet Explorer 7 or earlier—it only works on Internet Explorer 8+ or other major browsers.

fieldContext .elements

An array of HTML DOM nodes that are associated with your validator. Typically this will contain just one element—the form field whose value you are validating.

fieldContext .validationMessageElement

The HTML DOM node that will be used to display any validation message for this validator.

fieldContext .formContext .fields

An array containing all the fieldContext objects associated with the form. You can use this to inspect the state of other validators in the same form.

Reusing the Built-In Client-Side Validation Logic

If your custom validation logic is very simple, you don't necessarily need to create a whole new validation provider or write your own client-side validation code. All that work may be overkill, because it's often possible to build on an existing Data Annotations rule.

For example, earlier in this chapter we created a custom validator that validates e-mail addresses. Previously, we overrode the IsValid() method to implement custom logic, but a different way to do it would be to inherit from RegularExpressionAttribute.

public class ValidEmailAddressAttribute : RegularExpressionAttribute
{
    private const string EmailPattern = ".+@.+\..+";

    public ValidEmailAddressAttribute() : base(EmailPattern)
    {
        // Default message unless declared on the attribute
        ErrorMessage = "{0} must be a valid email address.";
    }
}

This will work for server-side validation immediately, but what might surprise you is that it won't give you client-side validation. This is because, although DataAnnotationsModelValidatorProvider can run anything inherited from ValidationAttribute on the server, it only knows how to generate client-side representations of the four specific subclasses listed in Table 12-9.

DataAnnotationsModelValidatorProvider has a system of adapters, which are delegates that can convert ValidationAttribute subclasses into ModelValidator instances. It has four built-in adapters:

  • RangeAttributeAdapter

  • RegularExpressionAttributeAdapter

  • RequiredAttributeAdapter

  • StringLengthAttributeAdapter

There are several possible ways of making DataAnnotationsModelValidatorProvider understand ValidEmailAddressAttribute, the easiest of which is simply to associate EmailAddressAttribute with RegularExpressionAttributeAdapter. Add code similar to the following to Application_Start() in Global.asax.cs:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    DataAnnotationsModelValidatorProvider.RegisterAdapter(
        typeof(ValidEmailAddressAttribute),
        typeof(RegularExpressionAttributeAdapter)
    );
}

Now, you can apply the [ValidEmailAddress] rule to get basic e-mail address validation both on the server and on the client.

Putting Your Model Layer in Charge of Validation

You understand ASP.NET MVC's mechanism for registering rule violations, displaying them in views, and retaining attempted values. You have also seen how to define validation rules using Data Annotations attributes or a custom validation provider, how the default model binder applies these rules during binding, and how you can map them all onto a client-side validation script.

So far in this chapter's appointment booking example, our ASP.NET MVC application has been in charge of enforcing validation rules. That's OK in a small application, but it does tightly couple the implementation of business logic to the nuances of a particular UI technology (i.e., ASP.NET MVC). Such tight coupling is accepted practice in ASP.NET Web Forms because of how that platform guides you with its built-in validator controls. However, it's not an ideal separation of concerns, and over time it leads to the following practical problems:

  • Repetition: You have to duplicate your rules in each view model to which they apply. Like any violation of the don't-repeat-yourself (DRY) principle, it creates extra work and opens up the possibility of inconsistencies.

  • Obscurity: It's all too easy for someone to add a new action method that accidentally forgets to check ModelState.IsValid and then accepts illegal data. Plus, if you have lots of different view models and no single central definition of your business rules, it's only a matter of time until you lose track of your intended design. You can't blame the new guy: nobody told him to enforce that obscure business rule in the new feature he just built.

  • Restricted technology choices: If your rules are defined by a mixture of Data Annotations attributes, custom ModelValidator classes, and arbitrary logic inside action methods, you can't just choose to build a new Silverlight client or native iPhone edition of your application without having to reimplement your business rules yet again (if you can even figure out what they are). Your validation rules may run inside ASP.NET MVC, but an arbitrary future technology probably won't run them.

  • Unnatural chasm between validation rules and business rules: It might be convenient to drop a [Required] attribute onto a view model, but what about rules such as "Usernames must be unique," or "Only 'Gold' customers may purchase this product when stock levels are low"? This is more than UI validation. But why should you implement such rules differently?

As described in Chapter 3, the solution to all these problems is to establish an independent domain model (most likely as a separate class library that doesn't know anything about ASP.NET MVC), and put it in control of validating the operations you perform against it.

For example, you may create a domain service class called AppointmentService in your domain class library project.[77] It may expose a method, CreateAppointment(), that saves new Appointment instances.

public static class AppointmentService
{
    public static void CreateAppointment(Appointment appt)
    {
        EnsureValidForCreation(appt);
        // To do: Now save the appointment to a database or wherever
    }

    private static void EnsureValidForCreation(Appointment appt)
    {
        var errors = new RulesException<Appointment>();

        if (string.IsNullOrEmpty(appt.ClientName))
            errors.ErrorFor(x => x.ClientName, "Please specify a name");

        if (appt.AppointmentDate < DateTime.Now.Date)
            errors.ErrorFor(x => x.AppointmentDate, "Can't book in the past");
        else if ((appt.AppointmentDate - DateTime.Now.Date).TotalDays > 7)
            errors.ErrorFor(x => x.AppointmentDate,
                            "Can't book more than a week in advance");

        if (appt.ClientName == "Steve"
            && appt.AppointmentDate.DayOfWeek == DayOfWeek.Saturday)
            errors.ErrorForModel("Steve can't book on weekends");

        if (errors.Errors.Any())
            throw errors;
    }
}

Tip

If you don't want to hard-code error messages inside your domain code, you could amend AppointmentService to fetch error messages from a RESX file at runtime. This would add support for localization as well as better configurability. You'll learn more about localization in Chapter 17.

Now the domain layer takes responsibility for enforcing its own rules. No matter how many different UI technologies try to create and save new Appointment objects, they'll all be subject to the same rules whether they like it or not. If the data isn't acceptable, the operation will be aborted with a RulesException.

But hang on a minute, what's a RulesException? This is just a custom exception type that can store a collection of error messages. You can put it into your domain model project and use it throughout your solution. Here it is:

public class RulesException : Exception
{
    public readonly IList<RuleViolation> Errors = new List<RuleViolation>();
    private readonly static Expression<Func<object, object>> thisObject = x => x;

    public void ErrorForModel(string message) {
Errors.Add(new RuleViolation { Property = thisObject, Message = message });
    }

    public class RuleViolation {
        public LambdaExpression Property { get; set; }
        public string Message { get; set; }
    }
}

// Strongly-typed version permits lambda expression syntax to reference properties
public class RulesException<TModel> : RulesException
{
    public void ErrorFor<TProperty>(Expression<Func<TModel, TProperty>> property,
                                    string message) {
        Errors.Add(new RuleViolation { Property = property, Message = message });
    }
}

Now you can update BookingController's MakeBooking() action so that it calls AppointmentService to save a new Appointment object and deals with any RulesException that may occur.

[HttpPost]
public ActionResult MakeBooking(Appointment appt, bool acceptsTerms)
{
    if (!acceptsTerms)
        ModelState.AddModelError("acceptsTerms", "You must accept the terms");

    try {
        if (ModelState.IsValid) // Not worth trying if we already know it's bad
            AppointmentService.CreateAppointment(appt);
    }
    catch (RulesException ex) {
        ex.CopyTo(ModelState); // To be implemented in a moment
    }

    if (ModelState.IsValid) {
        return View("Completed", appt);
    }
    else
        return View(); // Re-renders the same view so the user can fix the errors
}

To copy any error messages from a RulesException into ModelState, here's a helpful extension method that you can put inside your ASP.NET MVC project:

internal static class RulesViolationExceptionExtensions
{
    public static void CopyTo(this RulesException ex,
                              ModelStateDictionary modelState)
    {
        CopyTo(ex, modelState, null);
    }

    public static void CopyTo(this RulesException ex,
                              ModelStateDictionary modelState,
string prefix)
    {
        prefix = string.IsNullOrEmpty(prefix) ? "" : prefix + ".";
        foreach (var propertyError in ex.Errors) {
           string key = ExpressionHelper.GetExpressionText(propertyError.Property);
           modelState.AddModelError(prefix + key, propertyError.Message);
        }
    }
}

You can use the overload that accepts a prefix parameter if you are using a prefix when model-binding the incoming model object.

Following this pattern, it's easy to express arbitrarily sophisticated rules in plain C# code. You don't have to express complex rules as custom ModelValidator classes that wouldn't be respected by other technologies anyway. Your rules can even depend on other data (such as stock levels) or what roles the current user is in. It's just basic object-oriented programming—throwing an exception if you need to abort an operation.

Exceptions are the ideal mechanism for this job because they can't be ignored and they can contain a description of why the operation was rejected. Controllers don't need to be told in advance what errors to look for, or even at what points a RulesException might be thrown. As long as it happens within a try...catch block, error information will automatically bubble up to the UI without any extra work.

As an example of this, imagine that you have a new business requirement: you can only book one appointment for each day. The robust way to enforce this is as a UNIQUE constraint in your database for the column corresponding to Appointment's AppointmentDate property. Exactly how to do that is off topic for this example (it depends on what database platform you're using), but assuming you've done it, then any attempt to submit a clashing appointment would provoke a SqlException.

Update the AppointmentService class's Create() method to translate the SqlException into a RulesException, as follows:

public static void CreateAppointment(Appointment appt)
{
    EnsureValidForCreation(appt);

    try {
        // To do: Now save the appointment to a database or wherever
    }
    catch (SqlException ex)
    {
       if (ex.Message.Contains("IX_DATE_UNIQUE")) { // Name of my DB constraint
           var clash = new RulesException<Appointment>();
           clash.ErrorFor(x => x.AppointmentDate, "Sorry, already booked");
           throw clash;
       }
        throw; // Rethrow any other exceptions to avoid interfering with them
    }
}

This is a key benefit of model-based validation. You don't have to touch any of your controllers or views when you change or add business rules—new rules will automatically bubble up to every associated UI without further effort (as shown in Figure 12-14).

A new error from the domain layer appearing at the correct place in the UI

Figure 12-14. A new error from the domain layer appearing at the correct place in the UI

What About Client-Side Validation?

Just because your model layer enforces its own rules doesn't mean you have to stop using ASP.NET MVC's built-in validation support. I find it helpful to think of ASP.NET MVC's validation mechanism as a useful first line of defense that is especially good at generating a client-side validation script with virtually no work. It fits in neatly with the view model pattern (i.e., having simple view-specific models that exist only to transfer data between controllers and views and do not hold business logic): each view model class can use Data Annotations attributes to configure client-side validation.

But still, your domain layer shouldn't trust your UI layer to enforce business rules. The real enforcement code has to go into the domain using some technique like the one you've just seen.

Summary

In this chapter you took a detailed tour of the ASP.NET MVC facilities that relate to models and data entry. This include generating UIs from model metadata using template view helpers, defining custom metadata, parsing incoming data automatically using model binding, and performing validation in controllers, on the client, and in your model layer.

In the next chapter, you'll move on to combine many of these techniques to build sophisticated UIs, including multiple-step forms (wizards), CAPTCHA controls, reusable widgets that can include their own application logic, and even your own custom view engine.



[72] Simple types are those that can be converted from a string using TypeDescriptor.GetConverter().

[73] ASP.NET threads by default take their culture mode from the host server, but you can change it, either programmatically by assigning to Thread.CurrentThread.CurrentCulture, or in Web.config by adding a node such as <globalization culture="en-GB" /> inside <system.web>. See Chapter 17 for more about this, including how to autodetect each visitor's preferred culture setting.

[74] When you run in Release mode and don't have a debugger attached, .NET exceptions rarely cause any measurable performance degradation, unless you throw tens of thousands of exceptions per second.

[75] For an example of a dynamic list editor that lets users add or remove any number of items, see my blog post at http://tinyurl.com/mvclist. Internally, it uses DefaultModelBinder's support for nonsequential indexes.

[76] ASP.NET MVC 2 actually contains two ways of receiving an uploaded file. It has both HttpFileCollectionValueProvider and a custom model binder called HttpPostedFileBaseModelBinder. The custom model binder is really just a holdover from ASP.NET MVC 1, which didn't have such a neat system of value providers. As far as I understand, HttpPostedFileBaseModelBinder is deprecated and is likely to be removed in ASP.NET MVC 3.

[77] To keep this example focused, AppointmentServices is just a static class. In practice, you probably want to decouple your controllers from your service classes by having the services implement interfaces and using DI to inject an implementation at runtime. You saw an example of doing this with ProductsRepository in Chapter 4.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.205.214