Polymorphism and the Validation Application Block

The Validation Application Block is a useful piece of technology, but unfortunately it doesn't get along too well with that slippery character called Polymorphism. This is because you need to tell the block the exact type in which it should look for validators - it won't automatically figure out what the real type of your instance is.

For example, consider the following type definitions:

 public class BaseClass
{
    [StringLengthValidator(1, 5)]
    public virtual string A
    {
        get;
        set;
    }
}


public class DerivedClass : BaseClass
{
    [StringLengthValidator(1, 5)]
    public string B
    {
        get;
        set;
    }
}

Now if I validate an instance of DerivedClass using Validation.Validate<DerivedClass>, everything works as expected. However if I validate my DerivedClass instance using Validation.Validate<BaseClass>, it will completely ignore property B and its validators. And that is rarely a good thing.

Of course if you know that your instance is a DerivedClass, you can simply call Validation.Validate<DerivedClass>. But the whole point of polymorphism is that you normally won't know which specific concrete type you have. Luckily there is a simple solution, which is to use one of the overloads of ValidationFactory.CreateFactory that lets you specify the target type using a non-generic type parameter, as in the following example:

 [TestMethod]
public void ValidateActualTypeWithInvalidDerivedClass()
{
    BaseClass b = new DerivedClass() { A = "Valid", B = "Invalid" };
    Validator validator = ValidationFactory.CreateValidator(b.GetType());
    ValidationResults results = validator.Validate(b);
    Assert.IsFalse(results.IsValid);
    Assert.IsTrue(results.ToArray<ValidationResult>().Length == 1);
} 

This works well if you want to want to manually validate a specific instance. However things get even more tricky if you have a hierarchy of types that you want to validate with the help of the Object Validator. Consider the following type:

 public class ContainerClass
{
    [ObjectValidator]
    public BaseClass NestedClass
    {
        get;
        set;
    }
}

In this case I can set my NestedClass property to a BaseClass, a DerivedClass, or some other subclass which I haven't thought of yet. Ideally, whenever I ask the VAB to validate my ContainerClass, the rules should kick in for whatever flavour of class I choose to set in my NestedClass property. It would probably be possible to build a supercharged ObjectValidator attribute that does this, but failing that there isn't any way that I know of to apply my previous trick to get the block to look at the concrete type for validation rules.

However I did stumble across the following hack that seems to work well. I used "self validation" to create an additional method on the base class which effectively delegates validation to the derived class:

 [HasSelfValidation]
public class BaseClass
{
    [StringLengthValidator(1, 5)]
    public virtual string A
    {
        get;
        set;
    } 


    [SelfValidation]
    public virtual void Validate(ValidationResults results)
    {
        if (this.GetType() != typeof(BaseClass))
        {
            Validator validator = ValidationFactory.CreateValidator(this.GetType());
            results.AddAllResults(validator.Validate(this));
        }
    }
}

The "if" test in the self-validation method is necessary to prevent an endless loop with the self-validation calling itself. With this hack in place, the following test now passes. In fact, it's also possible to validate a DerivedClass using Validation.Validate<BaseClass> as well!

 [TestMethod]
public void ValidateContainerTypeWithInvalidDerivedClass()
{
    ContainerClass c = new ContainerClass() { NestedClass = new DerivedClass() { A="Valid", B="Invalid"} };
    ValidationResults results = Validation.Validate<ContainerClass>(c);
    Assert.IsFalse(results.IsValid);
    Assert.IsTrue(results.ToArray<ValidationResult>().Length == 1);
}

I admit this hack is a little ugly and ideally wouldn't be necessary. However it's also very simple and does not require any changes to the Validation Application Block source code, so it seems to be a good solution for now.