An Exercise In The Finer Points Of ASP.Net MVC 3 Model Aware Data Annotations

comments

Anyone who asks me will know that i love the simplicity and elegance of creating ASP.Net MVC’s custom data annotations when writing your own validation in ASP.Net MVC. One situation that can be a tricky one to dig yourself out of when coming to more advanced validation logic is writing custom validation attributes that can see/compare properties between itself and other model properties.

imageAnatomy of an ASP.Net MVC custom validation attribute

I’ve blogged before on how easy it is to write your own ASP.Net MVC Data Annotations for validation, when i wrote a post at the beginning of the year showing you how to write a data annotation to make sure checkboxes where ticked in the post Required CheckBox validation in ASP.Net MVC 2 onwards.

Basically writing your own validation attribute comes down to writing a class that inherits from ValidationAttribute and simple overloading the IsValid() method.

Model Class

public class MyModel
{
    [MyCustomValidator]
    public string MyPropertyToValidate { get; set; }
}

Validator Class

public class MyCustomValidator : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        return value != null
                && value is string
                && (string)value == "validatedValue";
    }
}

The above is so awesomely simple that it makes your fall in love with the MVC framework.

…But what about when you want to write a validator that uses other model properties to come to it’s conclusion of “IsValid”?…

Normally after facing the above question, you would freak out because your little IsValid() overridden method can’t see anything but the object/property that it is validating… Or can it?

ASP.Net MVC 3 onwards adds great functionality in the form of some overloads to the base validation class that supports some important reflection data that gets handed in a validation time. While working on a recent project at work with my colleague Mark Ellis we ran into this. Our head banging was the inspiration for this post.

A little story about Comparison

When wanting to find out more about the ability for validation attributes to talk to other model properties it would be good to take a look at an attribute that we know does this already every day; The CompareValidator.

The source code of the CompareValidator actually uses a different IsValid overload as shown below  courtesy of Reflector (the free version… read into that as you will RedGate…)

protected override ValidationResult IsValid(object value, ValidationContext context)
{
    var confirmValue = context.ObjectType.GetProperty(ConfirmProperty).GetValue(context.ObjectInstance, null);
    if (!Equals(value, confirmValue))
    {
        return new ValidationResult(FormatErrorMessage(context.DisplayName));
    }
    return null;
}

The important part to note here is the code used to pull out the confirmValue property and its model data:

var confirmValue = context.ObjectType.GetProperty(ConfirmProperty).GetValue(context.ObjectInstance, null);

This answers all the questions you where freaking out about, Why? because the magical context property that gets sent into the method is your model! This means that to access any of the other properties in your model you simply write something like this:

var myOtherProperty = context.ObjectType.GetProperty("modelPropertyName").GetValue(context.ObjectInstance, null);

And simply replace the above modelPropertyName with your model properties name/key.

Any long term readers of mine will know that I'm not the best when it comes to picking good examples for my code, and the following is probably much of the same (apologies).

IsMultiple Custom Attribute

So if you wanted to make a custom validation attribute to verify a property is a multiple of another model properties value, we could write the following:

Model Class

public class MyModel
{
    public int ValueToDivide { get; set; }

    [IsMultiple(CompareTo = "ValueToDivide")]
    public int MyPropertyToValidate { get; set; }
}

Validator Class

public class IsMultiple : ValidationAttribute
{
    public string CompareTo { get; set; }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var compareValue = context.ObjectType.GetProperty(CompareTo).GetValue(context.ObjectInstance, null);

        if (value==null
            || !(value is int)
            || compareValue == null
            || !(compareValue is int)
            // validate there is no remainder if we divide by our value
            || (int)compareValue % (int)value > 0)
        {
            return new ValidationResult(FormatErrorMessage(context.DisplayName));
        }
        return null;
    }
}

Pretty awesome hey? This is especially handy when comparing a value against a database value, such as checking if a database index is unique (or a user hasn’t signed up before)