ASP.NET MVC: Adding client-side validation to PropertiesMustMatchAttribute in ASP.NET MVC 3 Preview 1
This is the fourth post in what is becoming a mini-series:
- ASP.NET MVC: Adding client-side validation to ValidatePasswordLengthAttribute
- ASP.NET MVC: Adding client-side validation to ValidatePasswordLengthAttribute in ASP.NET MVC 3 Preview 1
- ASP.NET MVC: Adding client-side validation to PropertiesMustMatchAttribute
- ASP.NET MVC: Adding client-side validation to PropertiesMustMatchAttribute in ASP.NET MVC 3 Preview 1 (this post!)
- ASP.NET MVC: ValidatePasswordLength and PropertiesMustMatch in ASP.NET MVC 3 RC2
In this post we will review the MustMatch validator that we created in the last post and look at how we can simplify it using the new features in ASP.NET MVC 3 Preview 1.
One problem that we hit last time is that the ValidationAttribute.IsValid method only passes the value to validate. In .NET 4 there is a new overload of IsValid that passes a ValidationContext instance. Using this we can rewrite our server-side logic as part of the MustMatchAttribute class:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
object model = validationContext.ObjectInstance;
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(model);
object valueToMatch = properties.Find(PropertyToMatch, true /* ignoreCase */).GetValue(model);
bool match = Object.Equals(value, valueToMatch);
if (match)
return null;
return new ValidationResult(FormatErrorMessage(null));
}
Similarly, we can take advantage of IClientValidatable as before to move the code to hook up the client-side rules into the attribute class:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string propertyIdToMatch = GetFullHtmlFieldId(PropertyToMatch, context);
yield return new ModelClientMustMatchValidationRule(FormatErrorMessage(metadata.DisplayName), propertyIdToMatch);
}
private string GetFullHtmlFieldId(string partialFieldName, ControllerContext controllerContext)
{
ViewContext viewContext = (ViewContext)controllerContext;
return viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(partialFieldName);
}
With these changes in place we no longer need the adapter class. We’re essentially using the same logic that was in the adapter class previously, but we can now put it directly in the attribute so we don’t have to worry about throwing an exception if the adapter isn’t registered as we no longer need the extra adapter class!
Although these changes to the validation system in ASP.NET MVC 3 Preview 1 are not the biggest changes being introduced, I think they offer a great deal of simplification when writing custom validators by removing the need to write an adapter class (either to hook up client-side validation or to get extra context for the validation). This simplifies the code base and also means that you don’t have to worry about registering the adapter, which makes it simpler to reuse validation attributes across projects.