March 2010

Volume 25 Number 03

Extreme ASP.NET - Model Validation and Metadata in ASP.NET MVC 2

By K. Scott Allen | March 2010

One of the new features added to the ASP.NET MVC 2 release is the ability to validate user input on both the server and client. All you need to do is give the framework some information about the data you need validated, and the framework will take care of the hard work and details.

This feature is a tremendous boon for those of us who wrote custom validation code and custom model binders to perform simple model validation with ASP.NET MVC 1.0. In this article, I’ll look at the built-in validation support in ASP.NET MVC 2.

Before I discuss the new capabilities, however, I’m going to revisit the old methodology. The validation features in ASP.NET WebForms have served me well for many years. I think it’s useful to review them to understand what an ideal validation framework provides.

Controlling Validation

If you’ve ever used ASP.NET WebForms, you know it’s relatively easy to add validation logic to a WebForm. You express the validation rules using controls. For example, if you wanted to make sure the user enters some text into a TextBox control, you just add a RequiredFieldValidator control pointing to the TextBox, like this:

<form id="form1" runat="server">
  <asp:TextBox runat="server" ID="_userName" />
  <asp:RequiredFieldValidator runat="server" ControlToValidate="_userName"
                               ErrorMessage="Please enter a username" />
  <asp:Button runat="server" ID="_submit" Text="Submit" />
</form>

The RequiredFieldValidator encapsulates both client-side and server-side logic to ensure that the user provides a user name. To provide client-side validation, the control emits JavaScript into the client’s browser, and this script ensures that the user satisfies all the validation rules before posting the form back to the server.

Think about what these WebForm validation controls offer—they’re incredibly powerful!

  • You can declaratively express validation rules for a page in a single location.
  • Client validation prevents a round trip to the server if the user doesn’t fulfill the validation rules.
  • Server validation prevents a malicious user from circumventing the client script.
  • The server and client validation logic stay in sync without becoming a maintenance problem.

But in ASP.NET MVC, you can’t use these validation controls and remain faithful to the spirit of the MVC design pattern. Fortunately, with version 2 of the framework, there is something even better.

Controls vs. Models

You can think of a WebForm control, like the TextBox, as a simple container for user data. You can populate the control with an initial value and display that value to the user, and you can retrieve any value the user enters or edits by inspecting the control after a postback. When using the MVC design pattern, the M (model) plays this same role as a data container. You populate a model with information you need to deliver to a user, and it will carry back updated values into your application. Thus, the model is an ideal place to express validation rules and constraints.

Here’s an example that comes out-of-the-box. If you create a new ASP.NET MVC 2 application, one of the controllers you’ll find in the new project is the AccountController. It’s responsible for handling new user registration requests, as well as log-in and change-password requests. Each of these actions uses a dedicated model object. You can find these models in the AccountModels.cs file in the Models folder. For example, the RegisterModel class, without validation rules, looks like this:

public class RegisterModel
{
  public string UserName { get; set; }
  public string Email { get; set; }
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}

The Register action of the AccountController takes an instance of this RegisterModel class as a parameter:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    // ...
}

If the model is valid, the Register action forwards the model information to a service that can create a new user.

The RegisterModel model is a great example of a view-specific model, or view model. It’s not a model designed to work with a specific database table, Web service call or business object. Instead, it’s designed to work with a specific view (the Register.aspx view, part of which is shown in Figure 1). Each property on the model maps to an input control in the view. I encourage you to use view models, as they simplify many scenarios in MVC development, including validation.


Figure 1 Register Information

Of Models and Metadata

When the user enters account information in the Register view, the MVC framework ensures that the user provides a UserName and Email. The framework also ensures that the Password and ConfirmPassword strings match, and that the password is at least six characters long. How does it do all this? By inspecting and acting on metadata attached to the RegisterModel class. Figure 2 shows the RegisterModel class with its validation attributes showing.

Figure 2 The RegisterModel Class with Validation Attributes

[PropertiesMustMatch("Password", "ConfirmPassword", 
  ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel
{
  [Required]        
  public string UserName { get; set; }

  [Required]
  public string Email { get; set; }

  [Required]
  [ValidatePasswordLength]
  public string Password { get; set; }

  [Required]
  public string ConfirmPassword { get; set; }
}

When the user submits the Register view, the default model binder in ASP.NET MVC will try to build a new instance of the RegisterModel class to pass as the parameter to the AccountController’s Register action. The model binder retrieves information in the current request to populate the RegisterModel object. For example, it can automatically find the POST value of an HTML input control named UserName, and use that value to populate the UserName property of RegisterModel. This behavior has been in ASP.NET MVC since version 1.0, so it won’t be new if you’ve already used the framework.

What is new in version 2 is how the default model binder will also ask a metadata provider if there is any metadata available for the RegisterModel object. This process ultimately produces a ModelMetaData derived object whose purpose is to describe not only the validation rules associated with the model, but also information related to the display of the model in a view. ASP.NET team member Brad Wilson has an in-depth series of posts on how this model metadata can influence the display of a model through templates. The first post in the series is at bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html.

Once the model binder has a ModelMetaData object associated with the model, it can use the validation metadata inside to validate the model object. By default, ASP.NET MVC uses metadata from data annotation attributes like [Required]. Of course, ASP.NET MVC is pluggable and extensible, so if you want to devise a different source for model metadata, you can implement your own metadata provider. Ben Scheirman has some great information on this topic in an article titled “Customizing ASP.NET MVC 2—Metadata and Validation,” available at dotnetslackers.com/articles/aspnet/customizing-asp-net-mvc-2-metadata-and-validation.aspx.

Data Annotations

As a brief aside, you can build your own validation attributes, as we’ll see later, but [Required] is one of a number of standard validation attributes that live inside the System.ComponentModel.DataAnnotations assembly. Figure 3 shows a complete list of validation attributes from the annotations assembly.

Figure 3 Validation Attributes from the Annotations Assembly

Attribute Description
StringLength Specifies the maximum length of the string allowed in the data field.
Required Specifies that a data field value is required.
RegularExpression Specifies that a data field value must match the specified regular expression.
Range Specifies the numeric range constraints for the value of a data field.
DataType Specifies the name of an additional type to associate with a data field (one of the DataType enumerated values, like EmailAddress, Url or Password).

These data annotation attributes are quickly becoming pervasive across the Microsoft .NET Framework. Not only can you use these attributes in an ASP.NET MVC application, but ASP.NET Dynamic Data, Silverlight and Silverlight RIA services understand them as well.

Viewing Validations

With the validation metadata in place, errors will automatically appear in a view when the user enters incorrect data. Figure 4 shows what the Register view looks like when a user hits Register without supplying any information.


Figure 4 Validation Fail

The display in Figure 4 was built using some of the new HTML helpers in ASP.NET MVC 2, including the ValidationMessageFor helper. ValidationMessageFor controls the placement of a validation message when validation fails for a particular data field. Figure 5 shows an excerpt from Register.aspx demonstrating how to use the ValidationMessageFor and ValidationSummary helpers.

Figure 5 How to Use New HTML Helpers

<% using (Html.BeginForm()) { %>
    <%= Html.ValidationSummary(true, "Account creation was unsuccessful. " +
    "Please correct the errors and try again.") %>
    <div>
        <fieldset>
            <legend>Account Information</legend>
            
            <div class="editor-label">
                <%= Html.LabelFor(m => m.UserName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(m => m.UserName) %>
                <%= Html.ValidationMessageFor(m => m.UserName) %>
            </div>

Custom Validations

Not all of the validation attributes on the RegisterModel class are attributes from Microsoft’s data annotations assembly. The [PropertiesMustMatch] and [ValidatePasswordLength] are custom attributes you’ll find defined in the same AccountModel.cs file that holds the RegisterModel class. There is no need to worry about custom metadata providers or metadata classes if you just want to provide a custom validation rule. All you need to do is derive from the abstract class ValidationAttribute and provide an implementation for the IsValid method. The implementation of the ValidatePasswordLength attribute is shown in Figure 6.

Figure 6 Implementation of the ValidatePasswordLength Attribute

[AttributeUsage(AttributeTargets.Field | 
                AttributeTargets.Property, 
                AllowMultiple = false, 
                Inherited = true)]
public sealed class ValidatePasswordLengthAttribute 
    : ValidationAttribute
{
    private const string _defaultErrorMessage = 
        "’{0}’ must be at least {1} characters long.";

    private readonly int _minCharacters = 
        Membership.Provider.MinRequiredPasswordLength;

    public ValidatePasswordLengthAttribute()
        : base(_defaultErrorMessage)
    {
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, 
            ErrorMessageString,
            name, _minCharacters);
    }

    public override bool IsValid(object value)
    {
        string valueAsString = value as string;
        return (valueAsString != null && 
            valueAsString.Length >= _minCharacters);
    }
}

The other attribute, PropertiesMustMatch, is a great example of a validation attribute you can apply at the class level to perform cross-property validations.

Client Validation

The RegisterModel validation we’ve looked at so far all takes place on the server. Fortunately, it’s easy to enable validation on the client, too. I try to use client validation whenever possible because it can give a user quick feedback while offloading some work from my server. The server-side logic needs to stay in place, however, in case someone doesn’t have scripting enabled in a browser (or is intentionally trying to send bad data to the server).

Enabling client validation is a two-step process. Step 1 is making sure the view includes the proper validation scripts. All the scripts you need reside in the Scripts folder of a new MVC application. The MicrosoftAjax.js script is the core of the Microsoft AJAX libraries and is the first script you’ll need to include. The second script is MicrosoftMvcValidation.js. I generally add a ContentPlaceHolder to my MVC application’s master page to hold scripts, as shown here:

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat=
"server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type=
"text/css" />

    <asp:ContentPlaceHolder ID="Scripts" runat="server">
       
    </asp:ContentPlaceHolder>
    
</head>

A view can then include the scripts it needs using a Content control. The code below would ensure the validation scripts are present:

<asp:Content ContentPlaceHolderID="Scripts" runat="server">
    <script src="../../Scripts/MicrosoftAjax.js" 
            type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcValidation.js" 
            type="text/javascript"></script>
</asp:Content>

The second step in using client-side validation is to invoke the EnableClientValidation HTML helper method inside the view where you need validation support. Make sure to invoke this method before using the BeginForm HTML helper, as shown below:

<%
       Html.EnableClientValidation(); 
       using (Html.BeginForm())
        {
     %>
     
     <!-- the rest of the form ... -->
     
     <% } %>

Note that the client-side validation logic only works with the built-in validation attributes. For the Register view, this means the client-side validation will ensure the required fields are present, but will not know how to validate the password length or confirm that the two password fields match. Fortunately, it’s easy to add custom JavaScript validation logic that plugs in to the ASP.NET MVC JavaScript validation framework. Phil Haack has details on his blog entry, “ASP.NET MVC 2 Custom Validation,” located at haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.

Wrapping up, you can see that built-in support for common validation scenarios is a huge new addition for ASP.NET MVC 2. Not only are the validation rules easy to add via attributes on a model object, but the validation features themselves are flexible and easy to extend. Start taking advantage of these features to save time and lines of code with your next ASP.NET MVC application.


K. Scott Allen* is a member of the Pluralsight technical staff and the founder of OdeToCode. You can reach Allen at scott@OdeToCode.com, read his blog at odetocode.com/blogs/scott or follow him on Twitter at twitter.com/OdeToCode.*

Thanks to the following technical expert for reviewing this article: Brad Wilson