Friday, October 23, 2009

Fluent Validation


I was looking for a validation rules implementation methodology on my business entities without breaking the SRP. I was comfortable using the validation application block and had some techniques to integrate with my WPF applications. Recently I stopped on the codeplex site on a project that uses fluent interfaces and lambda expressions for building validation rules on business objects.  Jeremy Skinner has done a wonderful job by creating the Fluent Validation library. Using the library is easy and uses very little code for writing the validation rules.
I have created a sample application to demonstrate the usage of Fluent Validation.
public class Customer : BaseEntity<Guid>
{
    public string Name { get; set; }
    public string Email { get; set; }
    public IList<Order> Orders { get; set; }
    public Address Address { get; set; }
}
public class Order : BaseEntity<Guid>
{
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public decimal Amount { get; set; }
    public int Quantity { get; set; }
}
The next step is to create your Validator classes for the entities.
public class OrderValidator : AbstractValidator<Order>
{
    public OrderValidator()
    {
        RuleFor<string>(x => x.Name).NotNull().NotEmpty().WithMessage("Name cannot be null or empty").WithPropertyName("Name");
        RuleFor<string>(x => x.Name).Length(1, 25)
            .WithMessage("Name should be in the range 0 - 25").WithPropertyName("Name");
        RuleFor<Guid>(x => x.Id).NotEqual(Guid.Empty).WithMessage("Id cannot be empty").WithPropertyName("Id");
        RuleFor<DateTime>(x => x.Date).LessThan(DateTime.Today)
            .WithMessage("Order date cannot be greater than today's date").WithPropertyName("Date");
        RuleFor<DateTime>(x => x.Date).Must((x, y) => x.Date > new DateTime(1900, 01, 01))
            .WithMessage("Order date should be greater than Jan, 01 1900").WithPropertyName("Date");
    }
}
public class CustomerValidator : AbstractValidator<Customer>
{
    public CustomerValidator()
    {
        RuleFor<string>(x => x.Name).NotEmpty().NotNull()
            .WithMessage("Name cannot be empty or null").WithPropertyName("Name");
        RuleFor<string>(x => x.Name).Length(1, 25)
            .WithMessage("Name should be of length 1 - 25").WithPropertyName("Name");
        RuleFor<IList<Order>>(x => x.Orders).SetValidator(new OrderValidator());
        RuleFor<Address>(x => x.Address).SetValidator(new AddressValidator());
        RuleFor(x => x.Email).EmailAddress().WithMessage("Not a valid email").WithPropertyName("Email");
        RuleFor(x => x.Address).NotNull().WithMessage("Customer cannot be created without address").WithPropertyName("Address");
    }
}
Once the validators are defined you can validate the code like
[TestMethod]
public void Customer_with_invalid_email_id_is_created()
{
    ___Customer.Email = "invalid email";
    var __Results = ___CustomerValidator.Validate(___Customer);

    Assert.IsTrue(__Results.Errors.Count > 0);
    Assert.IsFalse(__Results.IsValid);
    Assert.IsTrue(__Results.Errors.Any(x => x.PropertyName == "Email"));

}

[TestMethod]
public void Customer_with_invalid_order_name_is_created()
{
    ___Customer.Orders[0].Name = "Invalid order name with more than 25 characters";
    var __Results = ___CustomerValidator.Validate(___Customer);

    Assert.IsTrue(__Results.Errors.Count > 0);
    Assert.IsFalse(__Results.IsValid);
    Assert.IsTrue(__Results.Errors.Any(x => x.PropertyName == "Orders[0].Name"));
}
I’m using DI for getting the reference to my validator classes.
Container = new UnityContainer()
                .RegisterType<IValidator<Customer>, CustomerValidator>()
                .RegisterType<IValidator<Address>, AddressValidator>()
                .RegisterType<IValidator<Order>, OrderValidator>();
The validator instance is retrieved in the code from the container as
___CustomerValidator = Container.Resolve<IValidator<Customer>>();

2 comments:

Michael Freidgeim said...

​have you consider EntLib Validation Application Block? Why you preferred fluentvalidation?

Pravin said...

Nice article, but one question - What does inheriting from BaseEntity do? I am not sure I followed it correctly.