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);
}