Validar
Injects IDataErrorInfo or INotifyDataErrorInfo code into a class at compile time.
Install / Use
/learn @fodyarchived/ValidarREADME
<img src="/package_icon.png" height="30px"> Validar.Fody
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.
Usage
See also Fody usage.
NuGet installation
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 to FodyWeavers.xml
Add <Validar/> to FodyWeavers.xml
<Weavers>
<Validar/>
</Weavers>
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; }
}
Validation template code
public class ValidationTemplate : IDataErrorInfo, INotifyDataErrorInfo
{
public ValidationTemplate(INotifyPropertyChanged target)
{
// Provide an implementation
}
// implementation of IDataErrorInfo
// 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);
}
// implementation of IDataErrorInfo
// implementation of INotifyDataErrorInfo
}
Validation Template
- Must be named
ValidationTemplate. - Namespace doesn't matter.
- Must implement either
IDataErrorInfoorINotifyDataErrorInfoor 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 add a [ValidationTemplateAttribute] to tell Validar where to look.
[assembly: ValidationTemplateAttribute(typeof(MyUtilsLibrary.ValidationTemplate))]
Validation Template Implementations
Custom ValidationTemplate implementations are supported. Here are some suggested implementations that enable 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<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));
}
}
}
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;
ValidationContex
Related Skills
node-connect
339.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
