A common validation requirement for ASP.NET MVC applications is to perform model-level validation, such as when you need to perform validation logic based on multiple properties of the model. For more information on ASP.NET MVC, see "Understanding the ASP.NET MVC Framework" and "Securing ASP.NET MVC." The Compare attribute provides simple equality-based comparisons, but that satisfies a fairly narrow need. When you need nearly unlimited comparisons, you can use the new IValidatableObject interface to compare multiple properties or perform any kind of validation logic that goes beyond looking at the value of a single property. To implement model-level validation with IValidatableObject, you have to implement only a single method, Validate.
Say that you have a Dog model that contains attributes of a dog, and it implements a Neutered and a Spayed property to identify which procedure the dog has had. (This may or may not be the best data schema design, but let's go with it. Clients can make strange demands sometimes, and they pay the bills.) Only male dogs are neutered, and only female dogs are spayed, so Neutered and Spayed should only be used with the appropriate gender of dog. This is not a validation operation that is easy to do with ASP.NET MVC 2, particularly since it could conceivably result in multiple validation errors.
We can add code to the model in the Dog class to take care of this model-level validation. The class implements the IValidatableObject interface as in the following code. This interface requires that the class implement a single method, Validate, which returns IEnumerable<ValidationResult>IEnumerable(Of ValidationResult).
The following code shows an implementation of the Validate method that makes use of the Neutered, Spayed, and Gender fields in the model to make sure that the procedure jives with the gender of the dog. The code checks to see if Spayed is true and Gender is Male; if so, it returns a Validation result about the problem and provides a custom error message. Then it does the same thing with Neutered and Gender, making sure that a poor female hasn't been neutered. Potentially this code could return multiple ValidationResult objects, so it uses the yield keyword to return them all, but that won't happen in this particular scenario.
// Make sure Spayed is a valid value
if (Spayed && Gender == "Male")
yield return new ValidationResult("A male dog can't be spayed.");
// Make sure Neutered is a valid value
if (Neutered && Gender == "Female")
yield return new ValidationResult("A female dog can't be neutered.");
The VB code does the same thing, except that VB doesn't yet have the yield keyword, so this code uses a generic List object to manage all the possible validation problems, and returns that IEnumerable(Of ValidationResult) object.
Dim results As List(Of ValidationResult) =
New List(Of ValidationResult)
' Make sure Spayed is a valid value
If Spayed AndAlso Gender = "Male" Then
"A male dog can't be spayed."))
' Make sure Neutered is a valid value
If Neutered AndAlso Gender = "Female" Then
"A female dog can't be neutered."))
When you run an application that uses this code and, for example, try to change a spayed female's gender to Male and submit the form, you'll get the appropriate validation error.
Model-level validation is a server-side validation technique, since code has to execute in the model. It doesn't easily lend itself to client-side validation, mainly because it requires working with multiple properties of the model. Nevertheless, it is an easy way implement complex validation if you can work with the slightly complex syntax of the Validate method.
There are plenty of other new validation features in ASP.NET MVC 3, including in the client-side jQuery code. MVC is about to the point where it can handle just about any kind of validation task you can throw at it, without your having to write a ton of complicated code. This alone is probably a good enough reason to move to MVC 3 if you haven't done so already!
This article is derived from an ASP.NET MVC course that I developed and wrote for AppDev.