When I had worked on ASP.NET MVC project I really liked how input is validated with Data Annotations attributes. And when I had to return to the Web Forms, and write a simple form with some validation, I was wondering how I lived before with standard validator controls. For me, it was never convenient, when I had to write an enormous amount of server tags just to state that “this is required field which accepts only numbers in specified range…”. Yes, there is nothing terrible in declaration of two or three validation controls instead of one. But, if I had a choice, I would like to write only one validator per field and keep all input validation logic as far as I can from the UI markup. And being a developer the code-only approach is most natural for me.
System.ComponentModel.DataAnnotations namespace was introduced in .NET 3.5, and now its classes are supported by wide range of technologies like WPF, Silverlight, RIA Services, ASP.NET MVC, ASP.NET Dynamic Data but not in Web Forms. I thought that someone had already implemented ready-to-use Validator Control with client-side validation, but after searching the Web and most popular open source hosting services I found nothing. Ok, not nothing, but implementations what I have found lacked client-side validation and had some other issues. So I decided to write my own Data Annotations Validator that will also support client-side validation.
Creating Data Annotations Validator Control
Server-Side
As I wanted to achieve compatibility with existing validation controls (new validator is not a replacement for an old ones, it is just an addition to them), it was decided to inherit from BaseValidator. This class does all necessary initialization on both client- and server-sides and exposes all necessary methods for overriding.
1 2 |
|
First of all EvaluateIsValid method of BaseValidator should be overridden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The only interesting aspect of this method is line 16. I’m using FormatErrorMessage method of ValidationAttribute to use all the goodness like support of resources and proper default error message formatting. So, now there is no need to invent something with error messages.
Next thing to deal with is where to get ValidationAttributes collection. There is System.Web.DynamicData.MetaTable class that could be used to retrieve attributes. It was introduced in the first versions of ASP.NET Dynamic Data and now in 4.0 version of Dynamic Data, MetaTable has a static method CreateTable which accepts Type as input parameter. Why using MetaTable, why not retrieve attributes of Type directly from PropertyInfo for specified property name? Because MetaTable also supports retrieving of custom attributes that are applied to property through MetadataTypeAttribute and merges attributes applied to property both in metadata class and entity class. And again, why inventing something new when everything that is needed is right here?
1 2 3 |
|
Now let’s look a bit into the future - if we place ObjectType property into DataAnnotationsValidator, it means that we should specify this property for every validator control on page. This is redundancy and leads to copy-pasting which is not acceptable. Lets step aside and create MetadataSource control that will act like metadata provider for validators on page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Here I also thought about DisplayAttribute which is used to format default error message. Now how ObjectType of MetadataSouce should be specified? Well, we can do it programmatically on Page_Load or do it.. programmatically with CodeExpressionBuilder to keep all control setup in one place.
1 2 3 |
|
Now, with existence of MetadataSource all fields of DataAnnotationsValidator are initialized in the OnInit method
1 2 3 4 5 6 7 8 9 10 11 12 |
|
This is all what was needed to provide server-side validation.
Client-Side
First of all, lets check what standard validator controls could be replaced with Data Annotations attributes.
Data Annotations Attribute | Standard Validator Control |
---|---|
RequiredAttribute | RequiredFieldValidator |
StringLengthAttribute | - |
RegularExpressionAttribute | RegularExpressionValidator |
- | CompareValidator |
RangeAttribute | RangeValidator |
I have no ideas how to replace CompareValidator and I don’t think it is so critical and necessary to think on it. Time to look how standard validator controls are working on the client side.
Every validator that works on the client side should override AddAttributesToRender method of BaseValidator class. This method adds some fields to resulting javascript object. For example, RequiredFieldValidator adds evaluationfunction and initialvalue fields.
1 2 3 4 5 6 7 8 9 |
|
And resulting javascript block for RequiredFieldValidator will look next:
1 2 3 4 5 6 7 8 |
|
After examining source code of standard validator controls I found that every control sets evaluationfunction field that states a name for a javascript function that actually performs validation on the client-side. RequiredFieldValidator evaluation function is represented below.
1 2 3 |
|
The val parameter is a validator object that was initialized with all the fields that were set in the AddAttributesToRender method. Plain and simple, if you need to supply your validator on client-side with some information override AddAttributesToRender and add what you want. To replace standard validators DataAnnotationsValidator is doing a little trick - it adds all standard evaluationfunction names, error messages and all necessary fields that are used by standard validation functions. Evaluation function of DataAnnotationsValidator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
This function is registered in the OnPreRender stage of control lifecycle.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The only thing that remains is to get list of fields and values that are needed for validation functions. Every Data Annotation validation attribute will have an Adapter class that stores an array of ClientValidationRule classes. ClientValidationRule is just a container for storing javascript object field names and evaluationfunction.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
And ValidationAttributeAdapter acts like a bridge between existing ValidationAttibute and its ClientValidationRules.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
All ValidationAttributeAdapter classes are registered within ValidationAttributeAdapterFactory in a Dictionary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
As I said previously, idea was borrowed directly from ASP.NET MVC, so if you are familiar with its validation mechanisms you don’t need to learn how it works here. Approach here is almost identical to ASP.NET MVC which was described well by Brad Wilson. As in ASP.NET MVC DAValidation.IClientValidatable interface exposes an extension point and we can now create a custom validation attribute, implement IClientValidatable interface, write validation function or mix existing ones and get both server- and client-side validation. There is a great set of validation attributes - Data Annotations Extensions created by Scott Kirkland so it is only client function must be changed in order to use them with DataAnnotationsValidator.
1 2 3 4 |
|
And that’s all, now we have fully functional control that makes validation with Data Annotations possible in the ASP.NET Web Forms universe.
Complete source code could be found on Codeplex. There you can download latest version of control and example project.