Monday, August 27, 2012

Fluent validations using the fluent decorator pattern


Validation on business objects is a responsibility that changes based on the requirements and sometimes gets added or removed based on the context.  The decorator design pattern can be utilized to provide various validations on business objects which can be attached to the object dynamically. In this post we’ll see how we can use the fluent decorator to implement fluent validations on an entity. We’ll create the base validate as a generic class so that it can be used on any entities in the application.
public class Order
{
    public string OrderNumber { get; set; }
    public int OrderId { get; set; }
    public string Description { get; set; }
    public Customer Customer { get; set; }
    public DateTime OrderDate { get; set; }
    public string ShipmentDetails { get; set; }
    public DateTime? RecievedDate { get; set; }
    public OrderStatus Status { get; set; }
}
We’ll be using the Order object and validate it in this sample.
The interface IEnityValidator is used as the signature for our validation objects.
public interface IEntityValidator
{
    bool Validate();
    string GetErrorMessage();
    bool IsValid();
    List<ValidationError> GetErrors();
}

public class ValidationError
{
    public ValidationError(string field, string message)
    {
        Field = field;
        Message = message;
    }

    public string Field { get; set; }
    public string Message { get; set; }
}

The abstract EnityValidationDecorator is the base class for the validation decorators. All our decorators will inherit this class and implement the Validate method to do validations on the object passed to the validator.
public abstract class EntityValidationDecorator : IEntityValidator
{
    protected T _Entity;
    protected IEntityValidator _Validator;
    protected StringBuilder _messageBuilder = new StringBuilder();
    protected List<ValidationError> _Errors = new List<ValidationError>();

    protected EntityValidationDecorator(IEntityValidator validator)
    {
        var entityValidator = validator as EntityValidationDecorator;
        if (entityValidator != null) _Entity = entityValidator._Entity;

        _Validator = validator;
    }

    protected EntityValidationDecorator(T entity, IEntityValidator validator)
    {
        _Entity = entity;
        _Validator = validator;
    }

    public abstract bool Validate();
    public abstract string GetErrorMessage();
    public List<ValidationError> GetErrors()
    {
        if (_Validator == null) return _Errors;
        _Errors.AddRange(_Validator.GetErrors());
        return _Errors;
    }
       
    public abstract bool IsValid();
}
Implementing the Order number validator.
public class OrderNumberValidator : EntityValidationDecorator<Order>
{
    public OrderNumberValidator(IEntityValidator validator) : base(validator)
    {
    }

    public override bool Validate()
    {
        _Validator.Validate();
        string validationMessage;
        var orderNumber = _Entity.OrderNumber;

        if(string.IsNullOrEmpty(orderNumber))
        {
            validationMessage = "Order number cannot be empty";
            GetErrors().Add(new ValidationError("OrderNumber", validationMessage));
            _messageBuilder.AppendLine(validationMessage);
            return false;
        }
        if(orderNumber.Length < 5)
        {
            validationMessage = "Order number should be greater than 5";
            GetErrors().Add(new ValidationError("OrderNumber", validationMessage));
            _messageBuilder.AppendLine(validationMessage);
            return false;
        }
        return true;
    }

    public override string GetErrorMessage()
    {
        return string.Concat(_Validator.GetErrorMessage(), _messageBuilder.ToString());
    }

    public override bool IsValid()
    {
        return !GetErrors().Any();
    }
}
Similarly we can use different kinds of validators for performing validations on our Order object. Later we can add fluent interface support to the validations by using extension methods as given below.
public static class OrderDecoratorExtensions
{
    public static EntityValidationDecorator<Order> AttachValidator(this Order order)
    {
        return new OrderValidator(order);
    }

    public static EntityValidationDecorator<Order> AddOrderNumberValidation(this EntityValidationDecorator<Order> validator)
    {
        return new OrderNumberValidator(validator);
    }

    public static EntityValidationDecorator<Order> AddOrderCustomerValidation(this EntityValidationDecorator<Order> validator)
    {
        return new OrderCustomerValidator(validator);
    }

    public static EntityValidationDecorator<Order> AddOrderDateValidation(this EntityValidationDecorator<Order> validator)
    {
        return new OrderDateValidator(validator);
    }
}
Testing the validators.
[TestMethod]
public void OrderNumberShouldNotBeEmpty()
{
    var order = new Order();
    var orderValidator = order.AttachValidator()
        .AddOrderNumberValidation()
        .AddOrderCustomerValidation()
        .AddOrderDateValidation();

    var isValid = orderValidator.Validate();
    var errorMessage = orderValidator.GetErrorMessage();
    Assert.IsTrue(errorMessage != string.Empty);
    Assert.IsFalse(isValid);
}

1 comment:

Vignesh Nair said...

Hi prajeesh,ur post was very useful to me..i have a query here,what i have to do if i have to validate my business object data against my data context data.for example,if my business object property Ordername has value as Test and my data context property values is Test1.here is a data mismatch i need to log this is validation error list along with the incorrect values...