Provides validation for XAML binding models.
Injects IDataErrorInfo or INotifyDataErrorInfo code into a class at compile time.
This is an add-in for Fody
It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.
See also Fody usage.
Install the Validar.Fody NuGet package and update the Fody NuGet package:
PM> Install-Package Fody
PM> Install-Package Validar.Fody
The Install-Package Fody
is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.
Add <Validar/>
to FodyWeavers.xml
<Weavers>
<Validar/>
</Weavers>
- 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; }
}
public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo
{
public ValidationTemplate(INotifyPropertyChanged target)
{
// Provide an implementation
}
// implementation of IDataErrorInfo
// implementation of INotifyDataErrorInfo
}
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);
}
// implementation of IDataErrorInfo
// implementation of INotifyDataErrorInfo
}
- Must be named
ValidationTemplate
. - Namespace doesn't matter.
- Must implement either
IDataErrorInfo
orINotifyDataErrorInfo
or both. - Have a instance constructor that takes a
INotifyPropertyChanged
. - Can be generic e.g.
ValidationTemplate<T> where T: INotifyPropertyChanged
If ValidationTemplate
exist in the current assembly they will be picked up automatically.
If ValidationTemplate
exist in a different assembly add a [ValidationTemplateAttribute]
to tell Validar where to look.
[assembly: ValidationTemplateAttribute(typeof(MyUtilsLibrary.ValidationTemplate))]
Custom ValidationTemplate
implementations are supported. Here are some suggested implementations that enable common validation libraries.
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<T> : IDataErrorInfo, INotifyDataErrorInfo where T : INotifyPropertyChanged
{
T target;
IValidator validator;
ValidationResult validationResult;
ValidationContext<T> context;
static ConcurrentDictionary<RuntimeTypeHandle, IValidator> validators = new ConcurrentDictionary<RuntimeTypeHandle, IValidator>();
public ValidationTemplate(T target)
{
this.target = target;
validator = GetValidator(target.GetType());
context = new ValidationContext<T>(target);
validationResult = validator.Validate(context);
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(context);
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));
}
}
}
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));
}
}
}
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));
}
}
}
Check Mark designed by Mateo Zlatar from The Noun Project.