Entity validation with visitors and extension methods
On the Yahoo ALT.NET group, an interesting conversation sprung up around the topic of validation. Entity validation can be a tricky beast, as validation rules typically depend on the context of the operation (persistence, business rules, etc.).
In complex scenarios, validation usually winds up using the Visitor pattern, but that pattern can be slightly convoluted to use from client code. With extension methods in C# 3.0, the Visitor pattern can be made a little easier.
Some simple validation
In our fictional e-commerce application, we have a simple Order object. Right now, all it contains are an identifier and the customer’s name that placed the order:
public class Order { public int Id { get; set; } public string Customer { get; set; } }
Nothing too fancy, but now the business owner comes along and requests some validation rules. Orders need to have an ID and a customer to be valid for persistence. That’s not too hard, I can just add a couple of methods to the Order class to accomplish this.
The other requirement is to have a list of broken rules in case the object isn’t valid, so the end user can fix any issues. Here’s what we came up with:
public class Order { public int Id { get; set; } public string Customer { get; set; } public bool IsValid() { return BrokenRules().Count() > 0; } public IEnumerable<string> BrokenRules() { if (Id < 0) yield return "Id cannot be less than 0."; if (string.IsNullOrEmpty(Customer)) yield return "Must include a customer."; yield break; } }
Still fairly simple, though I’m starting to bleed other concerns into my entity class, such as persistence validation. I’d rather not have persistence concerns mixed in with my domain model, it should be another concern altogether.
Using validators
Right now I have one context for validation, but what happens when the business owner requests display validation? In addition to that, my business owner now has a black list of customers she won’t sell to, so now I need to have a black list validation, but that’s really separate from display or persistence validation. I don’t want to keep adding these different validation rules to Order, as some rules are only valid in certain contexts.
One common solution is to use a validation class together with the Visitor pattern to validate arbitrary business/infrastructure rules. First, I’ll need to define a generic validation interface, as I have lots of entity classes that need validation (Order, Quote, Cart, etc.):
public interface IValidator<T> { bool IsValid(T entity); IEnumerable<string> BrokenRules(T entity); }
Some example validators might be “OrderPersistenceValidator : IValidator
public interface IValidatable<T> { bool Validate(IValidator<T> validator, out IEnumerable<string> brokenRules); } public class Order : IValidatable<Order> { public int Id { get; set; } public string Customer { get; set; } public bool Validate(IValidator<Order> validator, out IEnumerable<string> brokenRules) { brokenRules = validator.BrokenRules(this); return validator.IsValid(this); } }
I also created the “IValidatable” interface so I can keep track of what can be validated and what can’t. The original validation logic that was in Order is now pulled out to a separate class:
public class OrderPersistenceValidator : IValidator<Order> { public bool IsValid(Order entity) { return BrokenRules(entity).Count() > 0; } public IEnumerable<string> BrokenRules(Order entity) { if (entity.Id < 0) yield return "Id cannot be less than 0."; if (string.IsNullOrEmpty(entity.Customer)) yield return "Must include a customer."; yield break; } }
This class can now be in a completely different namespace or assembly, and now my validation logic is completely separate from my entities.
Extension method mixins
Client code is a little ugly with the Visitor pattern:
Order order = new Order(); OrderPersistenceValidator validator = new OrderPersistenceValidator(); IEnumerable<string> brokenRules; bool isValid = order.Validate(validator, out brokenRules);
It still seems a little strange to have to know about the correct validator to use. Elton wrote about a nice trick with Visitor and extension methods that I could use here. I can use an extension method for the Order type to wrap the creation of the validator class:
public static bool ValidatePersistence(this Order entity, out IEnumerable<string> brokenRules) { IValidator<Order> validator = new OrderPersistenceValidator(); return entity.Validate(validator, brokenRules); }
Now my client code is a little more bearable:
Order order = new Order(); IEnumerable<string> brokenRules; bool isValid = order.ValidatePersistence(out brokenRules);
My Order class doesn’t have any persistence validation logic, but with extension methods, I can make the client code unaware of which specific Validation class it needs.
A generic solution
Taking this one step further, I can use a Registry to register validators based on types, and create a more generic extension method that relies on constraints:
public class Validator { private static Dictionary<Type, object> _validators = new Dictionary<Type, object>(); public static void RegisterValidatorFor<T>(T entity, IValidator<T> validator) where T : IValidatable<T> { _validators.Add(entity.GetType(), validator); } public static IValidator<T> GetValidatorFor<T>(T entity) where T : IValidatable<T> { return _validators[entity.GetType()] as IValidator<T>; } public static bool Validate<T>(this T entity, out IEnumerable<string> brokenRules) where T : IValidatable<T> { IValidator<T> validator = Validator.GetValidatorFor(entity); return entity.Validate(validator, out brokenRules); } }
Now I can use the extension method on any type that implements IValidatable
Visitor patterns are useful when they’re really needed, as in the case of entity validation, but can be overkill sometimes. With extension methods in C# 3.0, I can remove some of the difficulties that Visitor pattern introduces.