This blog post is about a preview release of ASP.NET MVC 2. Details may change by the time the final release becomes available, so the code presented here may not necessarily work on future releases with slight modification.
In ASP.NET MVC 2 Preview 1, we added support for server-side validation via the DataAnnotation attributes.
In ASP.NET MVC 2 Preview 2, we enhanced the validation support to support client-side validation, as well as make validation extensible. We still ship with DataAnnotations support in the box, but now you can plug in a third party validation library and get validation support in your MVC applications.
In this blog post, I’m going to show the code for a validation provider for Enterprise Library’s Validation Application Block that works with MVC 2 Preview 2. This code is written against EntLib 4.1, the current release version as of the writing of this blog post. You will need to take references to Microsoft.Practices.EnterpriseLibrary.Common.dll and Microsoft.Practices.EnterpriseLibrary.Validation.dll to get the code in this sample to compile.
The extensibility classes
We have provided an abstract base class for all validator provider authors: ModelValidatorProvider. To write a validation provider for MVC 2, you derive a new class from ModelValidationProvider and implement its single method:
public abstract class ModelValidatorProvider { public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context); }
The usage is relatively straight forward: given a controller context (which describes the current request) and a ModelMetadata (which describes the object to be validated), you should return 0 or more validators to be run against the model.
A validator is a class which derives from the abstract base class ModelValidator. This class accepts the instances of ModelMetadata and ControllerContext as its constructor arguments, and exposes them via protected properties. Authors of validators have an abstract method they must implement (to do server-side validation) and a virtual method they may override (to provide client-side validation hints, which will be covered in another blog post):
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules() { return Enumerable.Empty<ModelClientValidationRule>(); } public abstract IEnumerable<ModelValidationResult> Validate(object container);
When a validator runs on the server side, it is provided the container, and returns 0 or more ModelValidationResult objects that describe validation failures.
The container that’s passed here is the parent object for the object you’re validating. So, for example, if you’re validating the FirstName property on a Contact class, the ModelMetadata that you were given in the constructor describes the FirstName property and has the value for the property in the “Model” property, and the container that’s passed here will be the instance of the Contact record.
The ModelValidationResult class contains two values: the error message and the member name that the message applies to. If the validation message applies directly to the thing you’re validating (i.e., “FirstName” in this case), then you will leave the member name blank or null; if the message applies to some sub-property of the thing you’re validating (for example, you’re doing a multi-property validation against the model itself), then member name should be set to tell MVC which field to show the error for.
EntLibValidatorProvider and EntLibValidatorWrapper
Our implementation for EntLibValidatorProvider is relatively simple:
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Microsoft.Practices.EnterpriseLibrary.Validation; public class EntLibValidatorProvider : ModelValidatorProvider { public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { Validator validator = ValidationFactory.CreateValidator(metadata.ModelType); if (validator != null) { yield return new EntLibValidatorWrapper(metadata, context, validator); } } }
EntLib’s ValidationFactory can create an EntLib Validator object for us for the given class we want to validate. If it does, then we’ll return a single instance of a new class called EntLibValidatorWrapper, which implements ModelValidator.
EntLib’s Validator object contains a Validate method, which we will call to validate the object. It returns a collection of EntLib ValidationResult objects, which we will convert into MVC’s ModelValidationResult. EntLib's ValidationResult allows nested validation results, so we'll write the converter function to be recursive to flatten the list out for MVC.
The code is again relatively straight forward:
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using Microsoft.Practices.EnterpriseLibrary.Validation; public class EntLibValidatorWrapper : ModelValidator { private Validator _validator; public EntLibValidatorWrapper(ModelMetadata metadata, ControllerContext context, Validator validator) : base(metadata, context) { _validator = validator; } public override IEnumerable<ModelValidationResult> Validate(object container) { return ConvertResults(_validator.Validate(Metadata.Model)); } private IEnumerable<ModelValidationResult> ConvertResults(IEnumerable<ValidationResult> validationResults) { if (validationResults != null) { foreach (ValidationResult validationResult in validationResults) { if (validationResult.NestedValidationResults != null) { foreach (ModelValidationResult result in ConvertResults(validationResult.NestedValidationResults)) { yield return result; } } yield return new ModelValidationResult { Message = validationResult.Message, MemberName = validationResult.Key }; } } } }
Now all that’s left is to register this with ModelValidatorProviders.Current and we’ve enabled server-side Enterprise Library validation support. We can decorate our models with EntLib’s validation attributes, and all model binding – implicit via action parameters, or explicit via calls to UpdateModel/TryUpdateModel on a controller – will now run EntLib’s validation automatically.
Why no client-side validation support?
The current design of EntLib’s Validation Application Block uses the Composite pattern; that is, when we ask for validation for an object, it returns back a single validator object that contains a list of all the validation work to be done. While this is very convenient from a normal usage scenario, the unfortunate side-effect is that we can’t “peek inside” to see what the individual validations are that it’s doing, and therefore can’t generate the appropriate client-side validation hints.