Custom Data Annotation Validator Part I : Server Code

Let's say you want to create a GreaterThan validation attribute and use it like so:

public class Trip
{
    [Required]        
    public DateTime StartDate { get; set; }
    
    [Required]
    [GreaterThan("StartDate")]
    public DateTime EndDate { get; set; }
}

The implementation would look something like this:

public class GreaterThanAttribute : ValidationAttribute
{
    public GreaterThanAttribute(string otherProperty)
        :base("{0} must be greater than {1}")
    {
        OtherProperty = otherProperty;
    }

    public string OtherProperty { get; set; }

    public override string FormatErrorMessage(string name)
    {            
        return string.Format(ErrorMessageString, name, OtherProperty);
    }

    protected override ValidationResult 
        IsValid(object firstValue, ValidationContext validationContext)
    {
        var firstComparable = firstValue as IComparable;
        var secondComparable = GetSecondComparable(validationContext);

        if (firstComparable != null && secondComparable != null)
        {
            if (firstComparable.CompareTo(secondComparable) < 1)
            {
                return new ValidationResult(
                    FormatErrorMessage(validationContext.DisplayName));
            }
        }               

        return ValidationResult.Success;
    }        

    protected IComparable GetSecondComparable(
        ValidationContext validationContext)
    {
        var propertyInfo = validationContext
                              .ObjectType
                              .GetProperty(OtherProperty);
        if (propertyInfo != null)
        {
            var secondValue = propertyInfo.GetValue(
                validationContext.ObjectInstance, null);
            return secondValue as IComparable;
        }
        return null;
    }
}

Notice the IsValid override available in .NET 4 adds a ValidationContext typed parameter, and this parameter makes it easier to look "outside the attribute" at the associated model type and model instance (validationContext.ObjectType.GetProperty(OtherProperty)). 

The validation context does have a shortcoming when it comes to model metadata, however. The context brings along the DisplayName value for the current property, but there is nothing available to easily get to metadata for the rest of the model. For example, when formatting the error string it would be nice to use the friendly name of both properties involved in validation, but we only have DisplayName for one. One possible solution is to dig it our from the model metadata provider.

protected string GetOtherDisplayName(ValidationContext validationContext)
{
    var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(
        null, validationContext.ObjectType, OtherProperty);
    if (metadata != null)
    {
        return metadata.GetDisplayName();
    }
    return OtherProperty;
}

Next up: adding client validation and using jQuery.validate.

Print | posted @ Tuesday, February 22, 2011 9:12 AM

Comments on this entry:

Gravatar # re: Custom Data Annotation Validator Part I : Server Code
by Maarten van der lee at 3/1/2011 9:23 AM

Isn't it possible to find the friendly name or other attributes, by using reflection? You already used reflection to get the value of the property.
  
Gravatar # re: Custom Data Annotation Validator Part I : Server Code
by scott at 3/1/2011 10:15 AM

@Maarten: Yes - could use reflection. If you are on a project that is using a custom metadata provider and not the attribute based provider, then the above code would still work.
  
Gravatar # re: Custom Data Annotation Validator Part I : Server Code
by Zote at 3/2/2011 7:38 AM

@Maarten: watch @bradwilson's Advanced ASP.NET MVC 3 Presentation. It's same sample, and with what you need.

In with url, https://bitbucket.org/zote/validacaomvc, I have this and others validators.
  
Gravatar # re: Custom Data Annotation Validator Part I : Server Code
by Doeke at 3/23/2011 4:27 PM

I would rename OtherProperty to OtherPropertyName. I'm not familiar with these validation attributes. I also would start the article with some indication to which library it's about ;-)
  
Comments have been closed on this topic.
Scott Allen
Posts - 869
Comments - 4493
Stories - 14