Wednesday, December 29, 2010

ASP.NET MVC 3 Custom Validation

Data annotations Rock! Using data annotations on your model classes make data validation (client and server) trivial. Just add the data annotation to you model class, wire up an edit/create view to your controller via the 'Add View' wizard, and you have client- and server-side validation with very little effort.

A little disclaimer here - the implementation below it not MVC 3 specific; however, I developed the solution using MVC 3, tested using MVC 3, and it works using MVC 3. I'm pretty sure it will work as-is using MVC 2.

What if you need validation that is not provided via data annotations out-of-box? You create your own. That's what this post is all about.

Creating your own validators that support client- and server-side validation is essentially a four-step process:

Create a custom attribute that extends the ValidationAttribute, or better yet, you extend one of the existing data annotations.

Create a custom validator that extends the DataAnnotationsModelValidator, where T is the type of custom attribute you created in step one.

Create a client-side script to handle the client-side validation.

Register the attribute/validator classes in your app's bootstrapper or Global.asax.

Side note: It seems that every time I have a good idea for a post, Phil Haack has beat me to the punch... and this pattern is also true in this scenario :-) For a similar article on the same subject, please see Phil's ASP.NET MVC 2 Custom Validation post.

This post is very code centric; therefore, if the concepts are unfamiliar or a brushing up is necessary, please take a look at Phil's post. Also, Brad Wilson has a few different blog posts/series on Data Annotations and ModelMetadata. Brad and Phil's knowledge of the ins and outs of anything ASP.NET MVC (and arguably C#) scares me...

The custom attribute and validator that I'm going to create is an Email validator. There are plenty examples on the net, including here, here, etc... What these email validator solutions don't provide is client-side validation. I will show you how. So, lets start with step one:

1. Create a Custom Attribute

Below are two solutions. They both result in the same functionality. The first solution is a full implementation that extends the ValidationAttribute class, while the second solution extends the RegularExpressionAttribute.

ddd
Again, whatever implementation you choose is up to you. Both implementations have the same resulting functionality - Email validation via data annotations.

2. Create a custom validator that extends the DataAnnotationsModelValidator
This class - the validator class - is the class that provides metadata to enable client-side validation. This class essentially transfers settings, in our case the ErrorMessage and Pattern properties of the EmailAttribute class, from the attribute decorations on your model object to the client-side consumer. The client-side consumer is what provides the client-side validation.

So now that we have the attribute and validator classes created, we need to...

3. Create a client-side script to handle the client-side validation
I feel like I'm cheating on you here. Out scenario - email validation - and it's implementation do not require us to write client-side script. I know, I know... if you feel robbed, you can create your own client-side script, and register is with the jQuery Validate plugin. I'll try and create a future post will another validator that requires writing client-side script and jQuery Validate registration, but for now we going to enjoy the luxuries of extending and using existing functionality. But, HOW do we get away with using existing client-side script?

This method 'retrieves' a collection of client validation rules.' And since we are essentially using a RegularExpressionAttribute class (via extension), we can use the client validation rules used by the RegularExpressionAttribute's Model Validator class via the ModelClientValidationRegexRule passing our Error Message and Regular Expression pattern.

This bring us to our last step...

4. Register the attribute/validator classes in your app's bootstrapper or Global.asax
To make all this sweetness happen, we need to register the attribute and validator classes when the app domain kicks off its like cycle. In your Global.asax's Application_Start method, register the attribue and validator classes using the following code snippet:

To use the attribute and validator classes use just created, you need to decorate you model/POCO classes with data annotations, including your EmailAttribute class. The following shows how the EmailAttribute is used (along with other data annotations) to decorated a property in your model class:

Once you create strongly typed Edit and/or Create Views using your models, include the script libraries, and run the application, you will see the fruits of your labor and get client-side validation. The screenshot below is an example.