Data validation is intended to provide certain well-defined guarantees for fitness, accuracy, and consistency for various kinds of user input into an application. For business applications, data validation can be defined through declarative data integrity rules or procedure-based business rules. Data that does not conform to these rules will negatively affect business process execution. System.ComponentModel.DataAnnotations namespace contains various DataAnnotation attributes that gives you a simple way to apply different validation rules on model data. These attributes are helpful for common validation requirements like Required, Range, RegularExpression, StringLength etc. However sometimes you require custom validation for custom business rules. In this tutorial, I will show you how to implement a custom validation attribute by inheriting the built in ValidationAttribute base class and will include functionality to output HTML5 data-* attributes for use with client side validation.
Table of Contents
Introduction
There are four distinct parts to creating a fully functional custom validator that works on both the client and the server. First we subclass ValidationAttribute and add our server side validation logic. Next we implement IClientValidatable on our attribute to allow HTML5 data-* attributes to be passed to the client. Thirdly, we write a custom javascript function that performs validation on the client. Finally, we create an adapter to transform the HTML5 attributes into a format that our custom function can understand. It sounds like a lot of work; but I assure you once you get started you will find it relatively straightforward.
Getting Started
For the sake of this tutorial, I’ve decided to implemeant a ValidBirthDate attribute that force user to enter a birth date which is less than the current date. Let’s begin by creating a new ASP.NET MVC project called CustomValidationAttributeDemo and then create a simple model called Customer.
Customer Model
using System.ComponentModel.DataAnnotations;
using CustomValidationAttributeDemo.ValidationAttributes;
namespace CustomValidationAttributeDemo.Models
{
public class Customer
{
[Display(Name = "Customer Id")]
[Required(ErrorMessage = "Customer ID is required")]
public long CustomerId { get; set; }
[Display(Name = "Company Name") ]
[Required(ErrorMessage = "Company Name is required")]
[StringLength(10, ErrorMessage = "Company Name should be less than or equal to ten characters.")]
public string CompanyName { get; set; }
[Display(Name = "Age")]
[Range(20, 40, ErrorMessage = "Customer Age should be between 20 to 40.")]
public int Age { get; set; }
[Display(Name = "Date of Birth")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}" , ApplyFormatInEditMode = true)]
[ValidBirthDate(ErrorMessage = "Birth Date can not be greater than current date")]
public DateTime BirthDate { get; set; }
}
}
The above model class is using a bunch of built in attributes along with our custom validation attribute ValidBirthDate. These attributes perform all the magical stuff for us and create both server and client side validation code automatically.
Custom ValidationAttribute
Validating our model server-side is the first step on the way to custom validation. Create a new folder called “ValidationAttributes”. Create a new class inside this newly created folder with the name ValidBirthDate
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace CustomValidationAttributeDemo.ValidationAttributes
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidBirthDate : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
DateTime _birthJoin = Convert.ToDateTime(value);
if (_birthJoin > DateTime.Now)
{
return new ValidationResult("Birth date can not be greater than current date.");
}
}
return ValidationResult.Success;
}
}
}
Our custom validator subclasses the built-in abstract class ValidationAttribute. The class is decorated with an AttributeUsage attribute that tell .NET how the attribute can be used. Here, the ValidBirthDate attribute is only permitted on properties and only a single instance of the attribute may appear on each property.
The next thing we need to do is actually implement the validation logic. This is done by overriding the IsValid method. It is generally a good idea to only validate if the property has a value, so we add a null check and return ValidationResult.Success if the value is null. Finally, we converted the value to DateTime and compared it with current date and if the value is greater than today’s date we return a validation failure with a message.
Customer View
Once your validation attribute is complete, create a view to test your validation attribute. In this case, I am going to create a simple create view to ask user to input date of birth along with other values. The Create customer view looks like this
@model CustomValidationAttributeDemo.Models.Customer
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
@using (Html.BeginForm("Create", "Customer", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary()
<fieldset>
<legend>Customer</legend>
<div class="editor-label">
@Html.LabelFor(model => model.CompanyName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.CompanyName)
@Html.ValidationMessageFor(model => model.CompanyName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Age)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Age)
@Html.ValidationMessageFor(model => model.Age)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.BirthDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.BirthDate)
@Html.ValidationMessageFor(model => model.BirthDate)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Once you’ve added the above code, build and run your project (F5) and you should have a failing validation on date of birth textbox which will fire when you hit the Create button.
At the moment, our validation is only fired server-side. We want to avoid the round trip if we can and bring in some client side validation. Microsoft has included JQuery and JQuery validation plugin with the MVC framework. Microsoft has also included their own validation abstraction called unobtrusive validation which sits on top of the JQuery validation plugin and provides the glue between the server-side data annotations and the client side validation plugin. Essentially the unobtrusive validation automatically adds rules and custom validation methods to the JQuery validation plugin.
Client Side Validation
To provide client side validation support for your custom validator you need to implement IClientValidatable interface. This interface has one method GetClientValidationRules which you need to override in your class.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace CustomValidationAttributeDemo.ValidationAttributes
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidBirthDate : ValidationAttribute, IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
DateTime _birthJoin = Convert.ToDateTime(value);
if (_birthJoin > DateTime.Now)
{
return new ValidationResult("Birth date can not be greater than current date.");
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule mvr = new ModelClientValidationRule();
mvr.ErrorMessage = "Birth Date can not be greater than current date";
mvr.ValidationType = "validbirthdate";
return new[] { mvr };
}
}
}
The GetClientValidationRules method that you need to implement has one simple function. Using the metadata parameter, you are required to construct one or more ModelClientValidationRules which are returned from the method and used by the framework to output the client-side HTML5 data-* attributes that are necessary for client-side validation. Therefore, ModelClientValidationRule must contain all data necessary for validation to be performed on the client.
The ModelClientValidationRule class has three properties, two of which must always be set: ErrorMessage and ValidationType. ValidationType is a string unique to the type of validator and is used as a key to get the right client side validation code. In the above example I have set ValidationType value to “validbirthdate” which is the client side function adapter required for perform client side validation.
Client Side Validation Function
Add a new javascript file validbirthdate.js in your “Scripts” directory (or subdirectory thereof if you prefer to keep all your js out of the way of the default js provided
$(function() {
jQuery.validator.addMethod('validbirthdate', function (value, element, params) {
var currentDate = new Date();
if (Date.parse(value) > currentDate) {
return false;
}
return true;
}, '');
jQuery.validator.unobtrusive.adapters.add('validbirthdate', function (options) {
options.rules['validbirthdate'] = {};
options.messages['validbirthdate'] = options.message;
});
}(jQuery));
In the end you need to import required JQuery validation libraries and your custom js file in Customer view file as follows:
@section Scripts
{
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/validbirthdate.js")" type="text/javascript"></script>
}
If you run your code and view source, you’ll notice that your inputs have some extra HTML5 attributes added to them. These attributes are written out because of the ModelClientValidationRule you’ve put together. They also get picked up by the unobtrusive javascript adapter and fed into the jQuery Validation function that you set up earlier.
<input
class="input-validation-error text-box single-line"
data-val="true"
data-val-date="The field Date of Birth must be a date."
data-val-required="The Date of Birth field is required."
data-val-validbirthdate="Birth Date can not be greater than current date"
id="BirthDate"
name="BirthDate"
type="date"
value="2015-07-31" />
Now you run your project and you will notice that validation is performed on client side without any server round trip.
Hopefully, you have a working solution at this point and you can go and customize it for your own project. If you have missed something or you are stuck somewhere in between please download the complete source code of the CustomValidationAttributeDemo project by clicking the Download Source Code button at the top of the page.
Waqas, thank you for this. I am currently testing this and got it working as long as the controller has the “return View(model)” in it. However, when trying to do an “e.preventDefault()” to try and stop the full submission as I am trying over Ajax, all 3 validations come up for “Company Name is required”, “The Age field is required” and “The Date of Birth field is required”. However, when I enter the Date of Birthday date, greater than todays date on purpose, it’s not throwing the “Birth Date can not be greater than current date” when I use the “e.preventDefault()”. Is there a way to get the custom validation attribute to show up on the client side, when using Ajax? Thanks
Hi, Thanks a lot, I have one question. Why should we use [Custom Validator] (server-side) along with [Unobsrusive] (client-side) at the same time and with the same exact code (validation filters, I mean)?
You only need to implement both if you want both server and client side validations. If you only want server side validations, you can ignore the client side validation part of my blog post.
Hi. Thanks for the article. I implemented the code. The server side validation is working. But the client side validation is not hitting. GetClientValidationRules is never called
tanks a Great article it my help full