DevTrends

The Complete Guide To Validation In ASP.NET MVC 3 - Part 1

The latest release of ASP.NET MVC (version 3) has a number of new validation features that significantly simplify both the validation code AND the html outputted to the client. This two-part article will attempt to cover all common validation scenarios and introduce all the new MVC3 validation features along the way. We will provide detailed explanations as well as full code examples that you can adapt for your own needs.

In part one of the article we will give an overview of validation in ASP.NET MVC 3. We will look at the built-in validators including the new CompareAttribute and RemoteAttribute and see what has changed from MVC 2, particularly on the client-side. In Part Two, we will write several custom validators that include both client and server-side validation. We will also look at an alternative to using data annotations - the IValidatableObject interface.

So let's get started...

Setting up Visual Studio

We will be adding all code to the 'Internet Application' template that comes with ASP.NET MVC 3, so if you wish to follow along you, just open up Visual Studio, select new ASP.NET MVC 3 Web Application and pick the Internet Application template when prompted.

The Visual Studio 2010 New Project Dialog

Figure 1: The Visual Studio 2010 New Project Dialog

The ASP.NET MVC3 Internet Application Template

Figure 2: The Visual Studio 2010 New ASP.NET MVC3 Internet Application Template

If you look at solution explorer, you will find a skeleton application with two controllers and a single file in the Models folder (AccountModels.cs) containing all view models. Open up this file, expand the Models region and you will find three models for registration, logon and change password. All examples in this article will revolve around the registration process and thus use the RegisterModel class.

The Internet Application Template

Figure 3: Initial Solution Explorer for Internet Application Template

Inspecting the model

public class RegisterModel
{
  [Required]
  [Display(Name = "User name")]
  public string UserName { get; set; }

  [Required]
  [DataType(DataType.EmailAddress)]
  [Display(Name = "Email address")]
  public string Email { get; set; }

  [Required]
  [ValidatePasswordLength]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }

  [DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
  public string ConfirmPassword { get; set; }
}

If you take a look at this model, you will find four properties decorated with a number of attributes. If you have worked with ASP.NET MVC version 2, you will probably recognise many of these as System.ComponentModel.DataAnnotations attributes. Some of these are used to affect appearance such as [Display] and [DataType]. The remaining attributes are used for validation. In the RegisterModel, three distinct validation atributes are used: [Required], [Compare] and [ValidatePasswordLength].

Given that throughout this series, we are dealing with data annotations based validation that exclusively use .NET attributes, I will refer to validators and attributes synonymously. So for example, [Required], RequiredAttribute and the Required validator all refer to the same System.ComponentModel.DataAnnotations.RequiredAttribute class.

RequiredAttribute is not, new having been present in MVC 2 but is by far the most commonly used validator. As the name implies, any field with a Required validator needs to have a value if it is to pass validation. The required attribute does not have any configuration properties other than the three common error message ones that we will discuss later in this article.

CompareAttribute is a new, very useful validator that is not actually part of System.ComponentModel.DataAnnotations, but has been added to the System.Web.Mvc DLL by the team. Whilst not particularly well named (the only comparison it makes is to check for equality, so perhaps EqualTo would be more obvious), it is easy to see from the usage that this validator checks that the value of one property equals the value of another property. You can see from the code, that the attribute takes in a string property which is the name of the other property that you are comparing. The classic usage of this type of validator is what we are using it for here: password confirmation.

ValidatePasswordLengthAttribute is a custom validator that is defined within the Internet Application template. If you scroll to the bottom of the AccountModels.cs file, you will see the source code for this validator which is very useful to look at when you come to write your own custom validators. The validator subclasses ValidationAttribute and implements the new IClientValidatable interface that is used to output validation-specific markup to the rendered view. In Part Two of this article, we will discuss much of the structure of ValidatePasswordLengthAttribute and use this class as a base when we come to write several custom validators of our own.

The Controller and View

If these are your first steps with MVC 3, take a look at the AccountController and Register.cshtml View. In terms of validation, you can see that there are no significant changes to either file from MVC 2. This is good and what we would expect - validation logic like all business logic belongs in the model.

If you are particularly observant, you may have noticed a change to the javascript libraries that we are using in our view. We will discuss this below.

What has changed for the client?

Before we run the application for the first time, let's make a few changes:

public class RegisterModel
{
  [Required(ErrorMessage="You forgot to enter a username.")]
  [Display(Name = "User name")]
  public string UserName { get; set; }

  [Required(ErrorMessage="Email is required (we promise not to spam you!).")]
  [DataType(DataType.EmailAddress)]
  [Display(Name = "Email address")]
  public string Email { get; set; }

  [Required]
  [ValidatePasswordLength]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }

  [DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
  public string ConfirmPassword { get; set; }
}

Here, we have just changed the error messages for some of the [Required] validators. For an explanation of all the options for customising error messages, see below.

All data annotations validators share three common properties related to the error message that is displayed on validation failure. You can use these properties in three different ways:

Do not specify any property.

[Required]

Leaving all three properties blank and the default error message built in to the validator will be used. As an example, for the RequiredAttribute on the UserName property, the default error message is The User name field is required.. Note that it is displaying 'User name' rather than UserName because the validator uses the Name property of the DisplayAttribute if present.

Set the ErrorMessage property

[Required(ErrorMessage="Email is required (we promise not to spam you)")]

If you do not need to support multiple languages, you can simply set your error message directly on the attribute and this will replace the default error message. You can also make the error message a format string. Depending on the validator, you can specify one or more placeholders that will be replaced with the name of the property (or the Name property of the DisplayAttribute if present). The definition displayed below would result in the message 'Email address is required (we promise not to spam you)'

[Display(Name = "Email address")]
[Required(ErrorMessage="{0} is required (we promise not to spam you)")]

Set the ErrorMessageResourceName and ErrorMessageResourceType properties

[Required(ErrorMessageResourceName="Login_Username_Required" ErrorMessageResourceType=typeof(ErrorMessages))]

If you prefer to put your error messages in a resource file, you can instruct the validator to retrieve the error message from there using these properties. Again you can use format strings in your resource file in the same way that you do when you are setting the ErrorMessage.

OK, now it is time to run the application and see what has changed on the client-side with MVC 3. Navigate to /Account/Register and try to submit the form with missing or invalid data. You may be surprised to find client-side (javascript) validation firing and using the error messages that you just modified in the model.

The Initial Registration Form

Figure 4: The Initial Registration Form

Client-side validation and the option to use unobtrusive javascript (see below) is controlled by two settings in the web.config. These will both be present in all new MVC 3 projects, but you will need to add them yourself if upgrading an MVC2 application. As an aside, I cannot say that I am a fan of using appSettings in this way. I don't know why the team didn't use a custom config section but I am sure there was probably a good reason for it.

<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

View the page source and you will see lots of attributes on each form control starting with data-. This is unobtrusive javascript in action. Instead of outputting inline javascript or a JSON blob as in previous releases, MVC3 by default uses this much cleaner syntax. These new attributes are a feature of HTML5, but are fully backward compatible with all modern browsers (including IE6). We will talk more about unobtrusive javascript later in the article.

<div class="editor-label">
  <label for="UserName">User name</label>
</div>
<div class="editor-field">
  <input data-val="true" data-val-required="You forgot to enter a username." 
    id="UserName" name="UserName" type="text" value="" />
  <span class="field-validation-valid" data-valmsg-for="UserName" 
    data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
  <label for="Email">Email address</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-required="Email is required (we promise not to spam you!)." 
    id="Email" name="Email" type="text" value="" />
  <span class="field-validation-valid" data-valmsg-for="Email" 
    data-valmsg-replace="true"></span>
</div>


<div class="editor-label">
  <label for="Password">Password</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-length="&amp;#39;Password&amp;#39; must be at least 6 characters long." 
    data-val-length-min="6" data-val-required="The Password field is required." 
    id="Password" name="Password" type="password" />
  <span class="field-validation-valid" data-valmsg-for="Password" 
    data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
  <label for="ConfirmPassword">Confirm password</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-equalto="The password and confirmation do not match." 
    data-val-equalto-other="*.Password" id="ConfirmPassword" 
    name="ConfirmPassword" type="password" />
  <span class="field-validation-valid" data-valmsg-for="ConfirmPassword" 
    data-valmsg-replace="true"></span>
</div>

You will also see that we are no longer required to reference MicrosoftAjax.js, MicrosoftMvc.js or MicrosoftMvcValidation.js. Instead, we are using jquery.validate.js and an adapter library built by the MVC team: jquery.validate.unobtrusive.

Since the announcement back in 2008, Microsoft has commited to using jQuery as part of their official development platform. Now in version 3 of MVC, we can see the effects of this commitment. Instead of duplicating functionality between jQuery and Microsoft Ajax libraries, jQuery has become the primary client-side library. All custom Microsoft functionality is built on top of jQuery with the jquery.validate.unobtrusive containing validation adapter logic and jquery.unobtrusive.ajax containing the necessary javascript for ajax forms and links. Note that the older libraries are still present in the Scripts folder but there is absolutely no reason to use them for new projects.

In order to created some separation between MVC and jQuery (validate), the MVC developers have decided to use HTML5 data-* attributes instead of jQuery validate's method of using css class names. Not only is this more semantically correct, but this approach also allows you to use completely different javascript libraries in the future without any change to the mvc framework itself. The downside is that this separation does necessitate the creation of another javascript file to bridge these differences. Microsoft's jquery.validate.unobtrusive contains all the adapters necessary to convert the HTML5 attributes that are outputted by the validators into jQuery.validate compatible syntax. When you come to write your own custom validation, you can make use of methods in this file to register you own adapters.

Exploring the other data annotations validation attributes

If you are already familiar with MVC 2, feel free to skip this section and move on to the next section where we talk about the Remote validator.

The Range and RegularExpression validators

So we can take a look at the other built-in validators, let's add a new Age property to our model.

[Required]
[Range(18, 65, ErrorMessage = "Sorry, you must be between 18 and 65 to register.")]
public int Age { get; set; }

We will also need to add an age field to the Register.cshtml view. Just copy the following somewhere between the fieldset tags.

<div class="editor-label">
  @Html.LabelFor(m => m.Age)
</div>
<div class="editor-field">
  @Html.TextBoxFor(m => m.Age)
  @Html.ValidationMessageFor(m => m.Age)
</div>

Note that we have added a RequiredAttribute and RangeAttribute to the age property. The Range validator does exactly what you would expect and in this case restricts the property to values between 18 and 65. If you re-run the code and enter an out of range age, you'll get error message we defined. Now try entering 'xyz'. You'll see a different error message 'The field Age must be a number.'. This is not the result of a ValidationAttribute. It is simply because we defined the age property as an integer and as such, we cannot assign a string to it. We have no control over this message, so if it is not to your liking, you will need to change your model slightly. To get over these model binding issues, many people advocate the use of strings for all view model properties. Model binding will always be successful in this way.

Strictly speaking, it is possible to change the type conversion error message, but it is not straightforward to do so and involves overriding framework components. Changing the type to a string is far simpler and has the same effect.

[Required]
[Range(18, 65, ErrorMessage = "Sorry, you must be between 18 and 65 to register.")]
[RegularExpression(@"\d{1,3}", ErrorMessage = "Please enter a valid age.")]
public string Age { get; set; }

We have changed the age property to a string so model binding always suceeds. In addition, we have added a regular expression validator that checks that the string is a valid number. We are now able to specify any error message we want here.

While we are here, lets also add a RegularExpressionAttribute to the Email property:

[RegularExpression("", ErrorMessage = "Please enter a valid email address.")]
public string Email { get; set; }

Please no comments about the efficacy of the email regular expression. Yes, there are better expressions that you can use. If you are interested, I would recommend looking here.

The StringLength validator

Finally, let's add a StringLengthAttribute to username:

[StringLength(12, MinimumLength = 6, ErrorMessage = "Username must be between 6 and 12 characters.")]
public string UserName { get; set; }

As you would expect, StringLength validates the number of characters that you can have in a string type property. You must specify a maximum number, but a minimum is optional.

[StringLength(12, ErrorMessage = "Username must be a maximum of 12 characters.")]
public string UserName { get; set; }

In a standard text input, you can limit the number of characters by using the maxlength attribute, so you might not see any point in specifying it on the model as well, but remember that maxlength is just client-side html and as such can be manipulated or ignored by anyone that chooses to do so. StringLength like all built-in data annotations validators enforces validation logic on both the client and server side.

Viewing the model changes on the UI

Below is the fully modified version of the RegisterModel

public class RegisterModel
{
  [Display(Name = "User name")]
  [Required(ErrorMessage = "You forgot to enter a username.")]
  [StringLength(12, MinimumLength = 6, 
    ErrorMessage = "Username must be between 6 and 12 characters.")]
  public string UserName { get; set; }

  [Display(Name = "Email address")]
  [DataType(DataType.EmailAddress)]
  [Required(ErrorMessage = "Email is required (we promise not to spam you!).")]
  [RegularExpression("", ErrorMessage = "Please enter a valid email address.")]
  public string Email { get; set; }

  [Display(Name = "Password")]
  [DataType(DataType.Password)]
  [Required]
  [ValidatePasswordLength]
  public string Password { get; set; }

  [Required]
  [Range(18, 65, ErrorMessage = "Sorry, you must be between 18 and 65 to register.")]
  [RegularExpression(@"\d{1,3}", ErrorMessage = "Please enter a valid age.")]
  public string Age { get; set; }

  [Display(Name = "Confirm password")]
  [DataType(DataType.Password)]
  [Compare("Password", 
    ErrorMessage = "The password and confirmation password do not match.")]
  public string ConfirmPassword { get; set; }
}

Re-run the application and confirm that your form has client-side validation for the new validators that you have applied.

The Modified Registration Form

Figure 5: The Modified Registration Form with an Age field

If you turn off JavaScript in your browser, you can test out the server side validation. You will find that the logic is exactly the same on client and server as you would expect. The server side validation will always fire ensuring that invalid data can never make it through to your business logic. For those users with JavaScript enabled, the client-side validation will provide immediate feedback resulting in a better experience for them and less load on the server for you.

<div class="editor-label">
  <label for="UserName">User name</label>
</div>
<div class="editor-field">
  <input data-val="true" data-val-length="Username must be between 6 and 12 characters." 
    data-val-length-max="12" data-val-length-min="6" 
    data-val-required="You forgot to enter a username." id="UserName" 
    name="UserName" type="text" value="" />
  <span class="field-validation-valid" data-valmsg-for="UserName" 
    data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
  <label for="Email">Email address</label>
</div>
<div class="editor-field">
  <input data-val="true" data-val-regex="Please enter a valid email address." 
    data-val-regex-pattern="\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b" 
    data-val-required="Email is required (we promise not to spam you!)." 
    id="Email" name="Email" type="text" value="" />
  <span class="field-validation-valid" data-valmsg-for="Email" 
    data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
  <label for="Age">Age</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-range="Sorry, you must be between 18 and 65 to register." 
    data-val-range-max="65" data-val-range-min="18" 
    data-val-regex="Please enter a valid age." data-val-regex-pattern="\d{1,3}" 
    data-val-required="The Age field is required." 
    id="Age" name="Age" type="text" value="" />
  <span class="field-validation-valid" data-valmsg-for="Age" 
    data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
  <label for="Password">Password</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-length="&amp;#39;Password&amp;#39; must be at least 6 characters long." 
    data-val-length-min="6" data-val-required="The Password field is required." 
    id="Password" name="Password" type="password" />
  <span class="field-validation-valid" data-valmsg-for="Password" 
    data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
  <label for="ConfirmPassword">Confirm password</label>
</div>
<div class="editor-field">
  <input data-val="true" 
    data-val-equalto="The password and confirmation do not match." 
    data-val-equalto-other="*.Password" id="ConfirmPassword" 
    name="ConfirmPassword" type="password" />
  <span class="field-validation-valid" 
    data-valmsg-for="ConfirmPassword" data-valmsg-replace="true"></span>
</div>

If you look at the html source, you can see all the HTML5 data attributes. It is interesting to look at the way these data attributes are constructed. Each form element that has validation has the following attributes:

data-valindicates that the field contains validation data and should be processed by the unobtrusive adapters script.
data-val-{validator name}e.g. data-val-length - contains the error message for the validator.
data-val-{validator name}-{argument name}e.g. data-val-length-min - zero or more arguments necessary for performing validation.

The data- prefix is defined by the HTML5 standard. The specification states 'Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements'. In the past, people tended to use hacks such as embedding data in css classes to add such data, but thankfully this is no longer necessary.

The Remote validator

Let's take a look at a brand new MVC 3 validator - RemoteAttribute. The Remote validator is very simple to use and yet extremely powerful. Remote is for situations where it is impossible to duplicate server side validation logic on the client, often because the validation involves connecting to a database or calling a service. If all your other validation uses javascript and responds to the user's input immediately, then it is not a good user experience to require a post back to validate one particular field. This is where the remote validator fits in.

In this example, we are going to add remote validation to the username field to check that it is unique.

[Remote("ValidateUserName", "Account", ErrorMessage = "Username is not available.")]
public string UserName { get; set; }

Remote has three constructors allowing you to specify either a routeName, a controller and action or a controller, action and area. Here we are passing controller and action and additionally overriding the error message.

Because the remote validator can be used to validate any field in any manner, the default error message is understandably vague. In this case, the error message would be 'UserName is invalid' which is not particularly useful to your end users, so always remember to override the default error message when using the RemoteAttribute.

The remote validator works by making an AJAX call from the client to a controller action with the value of the field being validated and optionally, the value of other fields. The controller action then returns a Json response indicating validation success or failure. Returning true from your action indicates that validation passed. Any other value indicates failure. If you return false, the error message specified in the attribute is used. If you return anything else such as a string or even an integer, it will be displayed as the error message. Unless you need your error message to be dynamic, it makes sense to return true or false and let the validator use the error message specified on the attribute. This is what we are doing here.

Our controller action is displayed below. To keep the example simple, we are just checking if the username is equal to 'duplicate' but in a real scenario, you would do your actual validation here making use of whatever database or other resources that you may require.

public ActionResult ValidateUserName(string username)
{
  return Json(!username.Equals("duplicate"), JsonRequestBehavior.AllowGet);
}

There are a few different arguments that can be passed to RemoteAttribute. Let's look at an alternative definition:

[Remote("ValidateUserNameRoute", HttpMethod="Post", AdditionalFields="Email", ErrorMessage = "Username is not available.")]
public string UserName { get; set; }

This time, we are passing a named route instead of controller and action. We have also changed the http method from the default (GET) to POST. Most interestingly, we are specifying that we need an additional field in order to perform validation. Just by specifying the AdditionalFields property on our model, the remote validator will automatically pass the values of these fields back to our controller action, with no further coding required in JavaScript or in .NET.

In our example, having email in addition to username is obviously of no use but you can imagine complex validation where multiple fields are required to validate successfully. When we include the AdditionalFields property in our validator declaration, we need to change our controller action to take in the additional field as arguments, so our controller action would become:

[HttpPost]
public ActionResult ValidateUserName(string username, string email)
{
  // put some validation involving username and email here
  return Json(true);
}

Run the application and try completing the completed form with valid and invalid usernames. As with all the built-in validators, client-side validation is enabled by default so as soon as you change the username and tab out of the field, an ajax request will be made to the controller action you specified in your remote validator declaration. This level of immediate feedback provides a great user experience and given the trivial amount of code required, I can see this validator being very popular.

The Remote validator

Figure 6: The RemoteAttribute validator in action

It is also important to note that you must ensure that the result of the controller action used by the remote validator is not be cached by the browser. How do we do this? Simply by decorating our controller action with the OutputCache attributes and explicitly setting it not to cache. Thanks to Ryan for this important step. You can read more about the remote validator on MSDN.

[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]

Conclusion

We have examined the use of validators in the Internet Application template of ASP.NET MVC 3. We have reviewed the usage of many of the in-built validators including the new RemoteAttribute and CompareAttribute. We have also looked at the significant changes to client-side validation including HTML5 data-* attributes and the move from MicrosoftAjax to jQuery and jQuery Validate.

In the Second Part of this article we will create several custom validators from scratch that implement validation logic on both the client and back-end. We will also investigate an alternative to data annotations, the IValidatableObject interface.

Useful or Interesting?

If you liked the article, we would really appreciate it if you would share it with your Twitter followers.

Share on Twitter

Comments

Comments are now closed for this article.