Thursday, November 5, 2009

Fluent Validation - WPF implementation


Working with FluentValidation makes my validation code cleaner and easier to integrate with the main application. I started removing my Enterprise library validation attributes and created Validators for my entities. The next major step was to integrate it with WPF. I have mentioned in one of my earlier posts on how to achieve this by implementing IDataErrorInfo on the business entities. Well, I had to follow the same approach for the FluentValidation classes also.
Here’s some sample code on the POC.
My BaseEntity class remains almost same, but with less code and looks clean
[Serializable]
public abstract class BaseEntity : INotifyPropertyChanged, IDataErrorInfo
{
    public T Id { get; set; }
    public Guid CreatedBy { get; set; }
    public Guid ChangedBy { get; set; }
    public DateTime CreatedDtTm { get; set; }
    public DateTime ChangedDtTm { get; set; }
    public Byte[] Version { get; set; }

    public void RaisePropertyChanged(params string[] propertyName)
    {
        propertyName.ToList().ForEach(x =>
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(x));
        });
    }

    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    public abstract ValidationResult SelfValidate();

    public bool IsValid
    {
        get { return SelfValidate().IsValid; }
    }

    public string Error
    {
        get { return ValidationHelper.GetError(SelfValidate()); }
    }

    public string this[string columnName]
    {
        get
        {
            var __ValidationResults = SelfValidate();
            if (__ValidationResults == null) return string.Empty;
            var __ColumnResults = __ValidationResults.Errors.FirstOrDefault<ValidationFailure>(x => string.Compare(x.PropertyName, columnName, true) == 0);
            return __ColumnResults != null ? __ColumnResults.ErrorMessage : string.Empty;
        }
    }
}
My Employee entity type inherits from the BaseEntity
public class Employee : BaseEntity<Guid>
{
    public string Code
    {
        get { return ___Code; }
        set
        {
            if (___Code != value)
            {
                ___Code = value;
                RaisePropertyChanged("Code");
            }
        }
    }

    public string FirstName
    {
        get { return ___FirstName; }
        set
        {
            if (___FirstName != value)
            {
                ___FirstName = value;
                RaisePropertyChanged("FirstName");
            }
        }
    }

    public string Email
    {
        get { return ___Email; }
        set
        {
            if (___Email != value)
            {
                ___Email = value;
                RaisePropertyChanged("Email");
            }
        }
    }
   

    public DateTime DateOfJoining
    {
        get { return ___DateOfJoining; }
        set
        {
            if (___DateOfJoining != value)
            {
                ___DateOfJoining = value;
                RaisePropertyChanged("DateOfJoining");
            }
        }
    }

    public List<EmployeeBenefit> Benefits { get; set; }

    public override ValidationResult SelfValidate()
    {
        return ValidationHelper.Validate<EmployeeValidator, Employee>(this);
    }

    string ___Code;
    string ___FirstName;
    string ___Email;
    DateTime ___DateOfJoining;
}
The validation logic for the Employee entity is available in the EmployeeValidator class
public class EmployeeValidator : AbstractValidator<Employee>
{
    public EmployeeValidator()
    {
        RuleFor<string>(x => x.Code).Length(5, 10)
            .WithMessage("Code" + ValidationConstants.StringLengthValidation + "5 - 10").WithPropertyName("Code");
        RuleFor<string>(x => x.FirstName).Length(1, 25)
            .WithMessage("FirstName" + ValidationConstants.StringLengthValidation + "1 - 25").WithPropertyName("FirstName");       
        RuleFor<string>(x => x.Email).EmailAddress()
            .WithMessage("Email" + ValidationConstants.NotValidValidation).WithPropertyName("Email");
        RuleFor<DateTime>(x => x.DateOfJoining).Must((x, y) => x.DateOfJoining < new DateTime(1900, 1, 1) || x.DateOfJoining > DateTime.Now)
            .WithMessage("Date of joining should be between 01-Jan-1900 and today").WithPropertyName("DateOfJoining");

    }
}
ValidationHelper class has two main methods that returns a string message for the failed validations and a Validator method to validate the entity.
public static ValidationResult Validate(K entity)
    where T : IValidator, new()
    where K : class
{
    IValidator __Validator = new T();
    return __Validator.Validate(entity);
}

public static string GetError(ValidationResult result)
{
    var __ValidationErrors = new StringBuilder();
    foreach (var validationFailure in result.Errors)
    {
        __ValidationErrors.Append(validationFailure.ErrorMessage);
        __ValidationErrors.Append(Environment.NewLine);
    }
    return __ValidationErrors.ToString();
}
Output





10 comments:

Alex Snowdon-Darling said...

Could you share this project? I'm struggling with understanding how fluentvalidation maps to WPF binding.

This is the only example I can find that uses FluentValidation and WPF, so the complete code would really help me out :-)

Alex S-D said...

And could you explain your ValidationHelper class...

I'm struggling to work out the
public static ValidationResult Validate bit...!

Alex S-D said...

I'm now staring to get to grips with your code... :-)

I believe the ValidationHelper class should be:
public static ValidationResult Validate(K entity)

Which was giving me some problems!
Now, I'm stuck on displaying the error message in the Validation.ErrorTemplate
Could you post the Style __TextBoxStyle for binding to FluentValidation rule error.

You code has really help so far, many thanks

Prajeesh Prathap said...

Hi Alex,

I have created a google Doc for with the style.

http://docs.google.com/Doc?docid=0Ae1x3DpC9y7fZHZxaG56aF8xYzd6ZnE2Mzk&hl=en

Prajeesh Prathap said...

The ValidationHelper class

http://docs.google.com/Doc?docid=0Ae1x3DpC9y7fZHZxaG56aF8yZmY5NHFoY2s&hl=en

Alex S-D said...

Thats great thanks... all is now working, and I have validation in my WPF MVVM project.

Could I ask another question :-)
Is it possible for the model to derive from an Entity Data Model instead?

public string this[string columnName] ...never gets called, so no binding happens?

http://docs.google.com/document/edit?id=1WdBAwmFKU6rOkl5UVmzZgiuEvXZCuUVRsqmv4qUFeic&hl=en_GB

Unknown said...

I have the same question that Alex-SD did.

Alex S-D said...

Hi Mauricio,

I posted my source code @ http://fluentvalidation.codeplex.com/Thread/View.aspx?ThreadId=212235

Hope this helps...

Prajeesh: How can validation be bound to a combobox? I thought it would be a simple case of making a style for the combobox and adding the validation.ErrorTemplate but it doesn't seem to work?

Any suggestions would be very helpful

Prajeesh Prathap said...

Did you try to add an adorner to the combobox?

Anonymous said...

how IsValid binded to the button?