Introducing Validar

Project Site: https://github.com/Fody/Validar/

In simplist terms Validar can be thought of as

"Doing for IDataErrorInfo and INotifyDataErrorInfo what NotifyPropertyWeaver does for INotifyPropertyChanged"

The main implementation difference is that Validar is an addin for Fody instead of a MSBuild Task and VSIX like NotifyPropertyWeaver.

There is a quick into to Fody here.

Using Validar

Add a ValidationTemplate

Validar works by merging your model class with an instance of a class named ValidationTemplate. You need one of these for your project. In this example I will use FluentValidation to do the actual validation. ValidationTemplate also needs to implement IDataErrorInfo or INotifyDataErrorInfo. In this case I will be implementing INotifyDataErrorInfo.

public class ValidationTemplate : INotifyDataErrorInfo
{
    INotifyPropertyChanged target;
    IValidator validator;
    ValidationResult validationResult;

    public ValidationTemplate(INotifyPropertyChanged target)
    {
        this.target = target;
        validator = ValidationFactory.GetValidator(target.GetType());
        validationResult = validator.Validate(target);
        target.PropertyChanged += Validate;
    }

    void Validate(object sender, PropertyChangedEventArgs e)
    {
        validationResult = validator.Validate(target);
        foreach (var error in validationResult.Errors)
        {
            RaiseErrorsChanged(error.PropertyName);
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        return validationResult.Errors
            .Where(x => x.PropertyName == propertyName)
            .Select(x => x.ErrorMessage);
    }

    public bool HasErrors
    {
        get { return validationResult.Errors.Count > 0; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    void RaiseErrorsChanged(string propertyName)
    {
        var handler = ErrorsChanged;
        if (handler != null)
        {
            handler(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }

}

ValidationFactory

You will note that in this example my ValidationTemplate uses a class named ValidationFactory. This class is to cache validator instances. This is an implementation specific detail to do with my choice of FluentValidation. Note that in my sample I assume the "XXXValidator" class exist in the same namespace as the model.

public static class ValidationFactory
{
    static Dictionary<RuntimeTypeHandle, IValidator> validators = new Dictionary<RuntimeTypeHandle, IValidator>();

    public static IValidator GetValidator(Type modelType)
    {
        IValidator validator;
        if (!validators.TryGetValue(modelType.TypeHandle, out validator))
        {
            var typeName = modelType.Name + "Validator";
            var type = Type.GetType(modelType.Namespace + "." + typeName, true);
            validator = (IValidator) Activator.CreateInstance(type);
        }

        return validator;
    }
}

Add an attribute

An attribute named InjectValidationAttribute is required to tell Validar which classes to inject in. You need to add this class to your project. It is not shipped as part of Validar

public class InjectValidationAttribute : Attribute
{
}

Model + Validator Pairs

Now you can write your models like this.

Note that your class needs to implement INotifyPropertyChanged. In these samples the actual INotifyPropertyChanged implementation excluded for brevity

[InjectValidation]
public class Person : INotifyPropertyChanged
{
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

And you can write a validator like this.

public class PersonValidator : AbstractValidator<Person>
{
    public PersonValidator()
    {
        RuleFor(x => x.FamilyName).NotEmpty();
        RuleFor(x => x.GivenNames).NotEmpty();
    }
}

What actually gets injected

The resultant compile code will look like this.

public class PersonTemplate : INotifyDataErrorInfo, INotifyPropertyChanged
{
    ValidationTemplate validationTemplate;

    public PersonTemplate()
    {
        validationTemplate = new ValidationTemplate(this);
    }

    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

    IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
    {
        return validationTemplate.GetErrors(propertyName);
    }

    bool INotifyDataErrorInfo.HasErrors
    {
        get { return validationTemplate.HasErrors; }
    }

    event EventHandler<DataErrorsChangedEventArgs> INotifyDataErrorInfo.ErrorsChanged
    {
        add { validationTemplate.ErrorsChanged += value; }
        remove { validationTemplate.ErrorsChanged -= value; }
    }

}

Early days

So it is still early days. Still at a 0.1 version. I am looking for feedback on the approach. So ping me on twitter http://twitter.com/SimonCropp.

Posted by: Simon Cropp
Last revised: 08 Feb, 2013 11:11 PM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.