Injects IDataErrorInfo or INotifyDataErrorInfo code into a class at compile time.
C#
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.github
ExternalValidation
Tests
Validar.Fody
Validar
WithGenericExternal
WithGenericInternal
WithNonGenericExternal
WithNonGenericInternal
.editorconfig
.gitattributes
.gitignore
Directory.Build.props
README.md
Validar.sln
Validar.sln.DotSettings
appveyor.yml
license.txt
package_icon.png

README.md

Chat on Gitter NuGet Status

Icon

This is an add-in for Fody

Provides validation for XAML binding models.

Injects IDataErrorInfo or INotifyDataErrorInfo code into a class at compile time.

Introduction to Fody

Usage

See also Fody usage.

NuGet installation

Install the Validar.Fody NuGet package and update the Fody NuGet package:

PM> Install-Package Validar.Fody
PM> Update-Package Fody

The Update-Package Fody is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.

Add to FodyWeavers.xml

Add <Validar/> to FodyWeavers.xml

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
  <Validar/>
</Weavers>

Your Model Code

  • Must implement INotifyPropertyChanged (in this case implementation excluded for brevity).
  • Contain a [InjectValidation] attribute.

For example

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

Your validation template code

public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo
{
    public ValidationTemplate(INotifyPropertyChanged target)
    {
        // Provide your own implementation
    }

    // Your implementation of IDataErrorInfo
    // Your implementation of INotifyDataErrorInfo
}

What gets compiled

Note that an instance of ValidationTemplate has been injected into Person

public class Person : INotifyPropertyChanged, IDataErrorInfo, INotifyDataErrorInfo 
{
    IDataErrorInfo validationTemplate;
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }

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

    // Your implementation of IDataErrorInfo
    // Your implementation of INotifyDataErrorInfo
}

Validation Template

  • Must be named ValidationTemplate.
  • Namespace doesn't matter.
  • Must implement either IDataErrorInfo or INotifyDataErrorInfo or both.
  • Have a instance constructor that takes a INotifyPropertyChanged.
  • Can be generic e.g. ValidationTemplate<T> where T: INotifyPropertyChanged

Current Assembly

If ValidationTemplate exist in the current assembly they will be picked up automatically.

Other Assembly

If ValidationTemplate exist in a different assembly You will need to use a [ValidationTemplateAttribute] to tell Validar where to look.

[assembly: ValidationTemplateAttribute(typeof(MyUtilsLibrary.ValidationTemplate))]

Validation Template Implementations

You can implement ValidationTemplate in any way you want. Here are some suggested implementations that will allow you to leverage common validation libraries.

FluentValidation

Install-Package FluentValidation

Note that FluentValidation extracts the model validation into a different class

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

public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo
{
    INotifyPropertyChanged target;
    IValidator validator;
    ValidationResult validationResult;
    static ConcurrentDictionary<RuntimeTypeHandle, IValidator> validators = new ConcurrentDictionary<RuntimeTypeHandle, IValidator>();

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

    static IValidator GetValidator(Type modelType)
    {
        if (!validators.TryGetValue(modelType.TypeHandle, out var validator))
        {
            var typeName = string.Format("{0}.{1}Validator", modelType.Namespace, modelType.Name);
            var type = modelType.Assembly.GetType(typeName, true);
            validators[modelType.TypeHandle] = validator = (IValidator)Activator.CreateInstance(type);
        }
        return validator;
    }

    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 string Error
    {
        get
        {
            var strings = validationResult.Errors.Select(x => x.ErrorMessage)
                                          .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public string this[string propertyName]
    {
        get
        {
            var strings = validationResult.Errors.Where(x => x.PropertyName == propertyName)
                                          .Select(x => x.ErrorMessage)
                                          .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

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

Sandra.SimpleValidator

Install-Package Sandra.SimpleValidator

Note that Sandra.SimpleValidator extracts the model validation into a different class

public class PersonValidator : ValidateThis<Person>
{
    public PersonValidator()
    {
        For(x => x.GivenNames)
            .Ensure(new Required().WithMessage("'Given Names' should not be empty."));

        For(x => x.FamilyName)
            .Ensure(new Required().WithMessage("'Family Name' should not be empty."));
    }
}

public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo
{
    INotifyPropertyChanged target;
    IModelValidator validator;
    ValidationResult validationResult;

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

    static ConcurrentDictionary<RuntimeTypeHandle, IModelValidator> validators = new ConcurrentDictionary<RuntimeTypeHandle, IModelValidator>();

    static IModelValidator GetValidator(Type modelType)
    {
        if (!validators.TryGetValue(modelType.TypeHandle, out var validator))
        {
            var typeName = string.Format("{0}.{1}Validator", modelType.Namespace, modelType.Name);
            var type = modelType.Assembly.GetType(typeName, true);
            validators[modelType.TypeHandle] = validator = (IModelValidator)Activator.CreateInstance(type);
        }
        return validator;
    }

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

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

    public bool HasErrors
    {
        get { return validationResult.IsInvalid; }
    }

    public string Error
    {
        get
        {
            var strings = validationResult.Messages.Select(x => x.Message)
                                          .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public string this[string propertyName]
    {
        get
        {
            var strings = validationResult.Messages.Where(x => x.PropertyName == propertyName)
                                          .Select(x => x.Message)
                                          .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

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

DataAnnotations

public class ValidationTemplate :
    IDataErrorInfo, 
    INotifyDataErrorInfo
{
    INotifyPropertyChanged target;
    ValidationContext validationContext;
    List<ValidationResult> validationResults;

    public ValidationTemplate(INotifyPropertyChanged target)
    {
        this.target = target;
        validationContext = new ValidationContext(target, null, null);
        validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(target, validationContext, validationResults, true);
        target.PropertyChanged += Validate;
    }

    void Validate(object sender, PropertyChangedEventArgs e)
    {
        validationResults.Clear();
        Validator.TryValidateObject(target, validationContext, validationResults, true);
        var hashSet = new HashSet<string>(validationResults.SelectMany(x => x.MemberNames));
        foreach (var error in hashSet)
        {
            RaiseErrorsChanged(error);
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        return validationResults.Where(x => x.MemberNames.Contains(propertyName))
                                .Select(x => x.ErrorMessage);
    }

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

    public string Error
    {
        get
        {
            var strings = validationResults.Select(x => x.ErrorMessage)
                                           .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public string this[string propertyName]
    {
        get
        {
            var strings = validationResults.Where(x => x.MemberNames.Contains(propertyName))
                                           .Select(x => x.ErrorMessage)
                                           .ToArray();
            return string.Join(Environment.NewLine, strings);
        }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

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

Icon

Check Mark designed by Mateo Zlatar from The Noun Project