Performing Custom Validations Using Data Annotations

Introduction

ASP.NET MVC applications can use data annotation attributes to enforce validations on model properties. ASP.NET MVC provides a few data annotations that cover frequently needed validations such as [Required], and [StringLength]. However, at times you need to perform custom validations on the model properties. In such cases you can create a custom data annotation attribute and attach it with a model property. You can also add client side validation capabilities to your custom data annotation. This article discusses how this task can be accomplished.

Creating the Required Model, Views and Controller

Before you develop a custom data annotation, first of all create a new ASP.NET MVC project and add an entity framework data model for the Employees table of the Northwind database. The following figure shows the Employee model class.

Employee Model Class
Employee Model Class

As you can see, the Employee class has many properties. For the sake of the example developed in this article you need only three: EmployeeID, FirstName and LastName. Once the data model is in place add a new controller to the project and write three action methods, viz. Index(), Edit() and Update() into it as shown below:

NorthwindEntities db = new NorthwindEntities();
 
public ActionResult Index()
{
  var data = from e in db.Employees
             select e;
  return View(data.ToList());
}
 
public ActionResult Edit(int id)
{
  var data = from e in db.Employees
             where e.EmployeeID==id
             select e;
  return View(data.SingleOrDefault());
}
 
public ActionResult Update(Employee e)
{
  if (ModelState.IsValid)
  {
    Employee obj = db.Employees.Find(e.EmployeeID);
    if (obj != null)
    {
      obj.FirstName = e.FirstName;
      obj.LastName = e.LastName;
      db.SaveChanges();
      ViewData["message"]="Data saved successfully!";
    }
  }
  return View("Edit",e);
}

The Index() action method selects all the employee records from the database and returns the Index view. A List of Employee objects is passed to the Index view as its data model. The Edit() method accepts an EmployeeID as its parameter and selects an Employee object based on the supplied EmployeeID. It then returns the Edit view and passes the selected Employee to the view as its data model. The Update() method accepts Employee as its parameter. The Edit view submits the form to the Update() action method. The Update() method checks whether the Employee model contains valid values or not using the IsValid property of the ModelState. If the model contains valid values it looks for an existing Employee using the Find() method. If Employee is found, its FirstName and LastName properties are changed to reflect the new values submitted by the user. The changes are saved to the database using the SaveChanges() method. A success message is stored in a ViewData variable and is later displayed on the Edit view. The Update() method returns the Edit view and passes the same Employee object as its model.

Now, add the Index view and design it as shown in the following figure:

Index View
Index View

As you can see the Index view displays a list of Employee records. There is a Show hyperlink for each employee row that points to the Edit() action method. The following markup shows the for loop that builds the employee list:

<table border="1" cellpadding="6">
  <% foreach(Employee emp in Model){ %>
  <tr>
    <td><%= emp.EmployeeID  %></td>
    <td><%= emp.FirstName %></td>
    <td><%= emp.LastName %></td>
    <td><%= Html.ActionLink("Show", "Edit", new { id=emp.EmployeeID })%></td>
  </tr>
<% } %>
</table>

Notice how the ActionLink() helper is used to render the Show link pointing to the Edit action method and also adds the EmployeeID as a route parameter.

Then add the Edit view and design it as shown below.

Edit View
Edit View

The Edit view displays the EmployeeID, FirstName and LastName columns using HTML helpers and submits the form to the Update() action method. Notice that the figure also shows a validation error. In the next section you will build a custom data annotation that adds this validation to the Employee model. The markup of the Edit view is shown below:

<% using(Html.BeginForm("Update","Home",FormMethod.Post)){ %>
<%= Html.LabelFor(e=>Model.EmployeeID) %>
<br />
<%= Html.TextBoxFor(e=>Model.EmployeeID,new {@readonly="readonly"}) %>
<br />
<%= Html.LabelFor(e=>Model.FirstName) %>
<br />
<%= Html.TextBoxFor(e=>Model.FirstName) %>
<%= Html.ValidationMessageFor(e=>Model.FirstName) %>
<br />
<%= Html.LabelFor(e=>Model.LastName) %>
<br />
<%= Html.TextBoxFor(e=>Model.LastName) %>
<%= Html.ValidationMessageFor(e=>Model.LastName) %>
<br />
<input type="submit" value="Update" />
<%}%>
<br />
<%= ViewData["message"] %>

As you can see the Edit view uses the LabelFor() and TextBoxFor() helpers to display EmployeeID, FirstName and LastName. Notice that a ValidationMessageFor() helper is used for FirstName and LastName to display any model validation errors. The EmployeeID is displayed in a read-only textbox since it’s the primary key. The EmployeeID textbox is made read-only by adding the readonly attribute.

Creating a Custom Data Annotation Attribute

Now that you have completed the controller and views, let’s proceed with model validations. Let’s assume that you wish to validate FirstName and LastName properties so that their length must be between 5 and 50 characters and they should contain only letters. You will build your own data annotation to accomplish this task. So, add a new class to the project and name it NameAttribute. The NameAttribute class inherits from the ValidationAttribute base class and is shown below:

 public class NameAttribute:ValidationAttribute
{
  private int intMinLength;
  public NameAttribute(int minlength)
  {
    this.intMinLength = minlength;
  }
  public override bool IsValid(object value)
  {
    string pattern = @"^[a-zA-Z]{" + intMinLength + ",50}$";
    bool result = Regex.IsMatch(value.ToString(),pattern, RegexOptions.IgnoreCase);
    if (result)
    {
      return true;
    }
    else
    {
      return false;
    }
  }
}

The NameAttribute class has a private member intMinLength that stores the minimum length of a name (you will use 5 in this example). The NameAttribute class inherits from the ValidationAttribute base class and overrides the IsValid() method. The IsValid() method accepts a value that is to be validated and returns true if the value is valid, false otherwise. The validation is performed using a regular expression. The regular expression ensures that the value contains only uppercase or lowercase letters and that its length is between intMinLength and 50. The IsMatch() method of the Regex class performs the pattern matching and returns true if the pattern is found in the source string. The IsMatch() returns false if the pattern cannot be found. The result is then returned to the caller.

Using a Custom Data Annotation Attribute

Once the custom data annotation class NameAttribute is ready it can be used on the data model for performing the validations. Since Employee class is an EF designer generated class you will need to create a metadata class and apply data annotations to its properties. The EmployeeMetadata class that does this for you is shown below:

public class EmployeeMetadata
{
  [Required]
  [Name(5,ErrorMessage="FirstName must be between 5 and 50 characters")]
  public string FirstName { get; set; }
 
  [Required]
  [Name(5, ErrorMessage = "LastName must be between 5 and 50 characters")]
  public string LastName { get; set; }
}

The EmployeeMetadata class defines two properties – FirstName and LastName. Both the properties are decorated with [Required] and [Name] data annotation attributes. Notice how [Name] is used to specify the minimum length of 5 and an ErrorMessage. The ErrorMessage property comes to the NameAttribute class from the base class ValidationAttribute. Currently the EmployeeMetadata class is not associated with the Employee model class in any way. To attach it with the model class you need to create an Employee partial class and use the [MetadataType] attribute to associate EmployeeMetadata with it. The following code shows how this is done.

[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee
{
}

Now you can run the Index action and test whether the FirstName and LastName values are being validated as expected.

Adding Client Side Validation Capabilities

Currently the NameAttribute class performs the validation on the server side. With some extra effort you can add client side validation capabilities to the [Name] data annotation. Let’s see how that can be done.

In order to add client side validation capabilities you need to implement the IClientValidatable interface on the NameAttribute class and implement its GetClientValidationRules() method. The following code shows the GetClientValidationRules() method implemented in the NameAttribute class.

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
  List<ModelClientValidationRule> rules = new List<ModelClientValidationRule>();
  ModelClientValidationRule rule = new ModelClientValidationRule();
  rule.ErrorMessage = metadata.GetDisplayName() + " is invalid!!!";
  rule.ValidationParameters.Add("minlength", intMinLength);
  rule.ValidationType = "name";
  rules.Add(rule);
  return rules;
}

The GetClientValidationRules() method returns IEnumerable of ModelClientValidationRule instances. Inside, it creates a List of ModelClientValidationRule. It then proceeds to create a ModelClientValidationRule and assigns its ErrorMessage, ValidationParameters and ValidationType properties. The ErrorMessage property indicates the client side error message that you would like to display. In most of the cases you will keep the client side and server side message to the same string value. In the above example a different message is used so as to identify whether the validation is being executed on the client side or on the server side. The ValidationParameters collection contains key value pairs representing the parameters that are passed to a client side function for the sake of validation. The ValidationType is a unique tag given to a particular type of validation. The parameter name and the ValidationType must be specified in lowercase and without any special characters. Finally, the ModelClientValidationRule is added to the List and returned to the caller.

Next, you need to add a JavaScript file that will contain the actual client validation logic. So, add a file named NameValidationScript.js and add the following code to it:

/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />
 
$.validator.unobtrusive.adapters.addSingleVal("name", "minlength");
 
$.validator.addMethod("name", function (value, element, minlength) {
    if (value) {
        var regex = new RegExp("^[a-zA-Z]{" + minlength + ",50}$");
 
        if (regex.test(value)) {
            return true;
        }
        else {
            return false;
        }
    }
    return true;
});

We won’t go into detail of this code but you can easily see that the code contains the equivalent client side validation logic. Notice that addSingleVal() and addMethod() use the same ValidationType and ValidationParameter as in the GetClientValidationRules() method.

For the client side validation to work you also need to refer a few scripts in the Edit view. The following markup shows these script references.

 <script src='<%= Url.Content("~/Scripts/jquery-1.7.1.js") %>'></script>
<script src='<%= Url.Content("~/Scripts/jquery.validate.js") %>'></script>
<script src='<%= Url.Content("~/Scripts/jquery.validate.unobtrusive.js") %>'></script>
<script src='<%= Url.Content("~/Scripts/NameValidationScript.js") %>'></script>

As you can see, the jQuery library is referenced followed by jquery.validate and jquery.validate.unobtrusive script files. Finally, your client side script is referenced.

That’s it! You can now run the page and test the validation again. This time you will observe that the validation errors are captured on the client side itself and the error message as specified in the GetClientValidationRules() is displayed.

Summary

Data annotation attributes allow you to validate model properties. In addition to inbuilt data annotations you can create your own data annotations by creating a class that inherits from the ValidationAttribute base class. Moreover you can also add client side validation capabilities to the custom data annotation by implementing the IClientValidatable interface.

About the Author:

Bipin Joshi is a blogger, author and an IT trainer who writes about apparently unrelated topics – Yoga & technology! Bipin has been programming since 1995 and is working with .NET framework ever since its inception. He has authored or co-authored half a dozen books and numerous articles on .NET technologies. He has also penned a few books on Yoga. You can read more about him here.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read