Are you valid?

The next thing we wanted to implement to our Core project was a business object validation. There are several paths we could take:

Because we qualified this application as a learning project, and we haven't used VAB before, we decided to try it. VAB enables developers to attach validation rules to properties in declarative way, by using attributes. Rules can also be defined in config file, allowing the simple rules change after the application is deployed, but that wasn't a requirement for this project.

Most of our entity classes now have properties that look like this:

[NotNullValidator]

[StringLengthValidator(3, 15,

    MessageTemplateResourceName = "ProductCodeLength",

    MessageTemplateResourceType = typeof (Product))]

public virtual string Code { get; set; }

 

[NotNullValidator]

[StringLengthValidator(1, 50,

    MessageTemplateResourceName = "ProductNameLength",

    MessageTemplateResourceType = typeof (Product))]

public virtual string Name { get; set; }

In order to avoid cluttering of code with localized error messages, they are moved to project resources, hence the MessageTemplateResourceName and MessageTemplateResourceType arguments.

And now the big thing... In order to check the object validity, IsValid() method is added to Entity<T> class (base class for all our entity objects, defined as Entity<T> where T: Entity<T>). IsValid is calling VAB Validator to check the property values on the object and returns true if object is valid. The first version looked like this:

public virtual bool IsValid()

{

    return Validation.Validate((T)this);

}

Validation class is a façade. It creates a Validator object and tests for validity in a single line of code. Validate method is actually generic - Validation.Validate<T>((T)this), but T argument is redundant.

The cast to type T was necessary, because the Validator isn't smart enough to go down the inheritance hierarchy. If we call Validate(this), it would try to find validation rules on the current class (Entity<T>), but not on inheriting classes. I.e. public class Product : Entity<Product>. By having a cast to T (Product in this case), we ensure that the rules are checked on the Product class.

This worked fine up until recently, when our entity model became more advanced:

public class Warehouse : Entity<Warehouse>

...

public class CommissionerWarehouse : Warehouse

...

IsValid method couldn't handle the CommissionerWarehouse class. The cast to T in Validate method gave it only a Warehouse type to check. CommissionerWarehouse rules were ignored. In order to handle this case, IsValid was rewritten to use the Validator object directly:

public virtual bool IsValid()

{

    var entityValidator = ValidationFactory.CreateValidator(GetType());

    return entityValidator.Validate(this).IsValid;

}

The code is twice as long as before, but I can live with it :)

The magic of this method allows easier Unit Testing (more on that later) and validation calls from the client without the reference to Validation Application Block assemblies. We could add some method to return error messages too, but that's not necessary, since we are using Martin Bennedik's WPF Integration for VAB.


0 comments:

Post a Comment