Introduction
In the following image the validation is fired even though the Check box (Experienced condition) is false and textbox is disabled.
In the following gif the validation is fired only when the check box (Experienced condition) is true.
We will see how conditional validation can be implemented in wpf using attributes.
Background
Out of various ways in which Validation in WPF can be done, I have implemented IDataErrorInfo to facilitate validation.
Using the code
In this example we are going to see how to extend Range attribute to support conditional validation and tweak IDataErrorInfo implementation.
Extension of RangeAttribute.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
[Conditional]
public class RangeIfAttribute : RangeAttribute
{
private string _dependentProperty;
public RangeIfAttribute(double minimum, double maximum, string dependentProperty)
: base(minimum, maximum)
{
this._dependentProperty = dependentProperty;
}
public RangeIfAttribute(int minimum, int maximum, string dependentProperty)
: base(minimum, maximum)
{
this._dependentProperty = dependentProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this._dependentProperty);
bool dependentvalue = (bool)field.GetValue(validationContext.ObjectInstance);
if (dependentvalue)
{
return base.IsValid(value, validationContext);
}
return ValidationResult.Success;
}
}
Viewmodel properties decorated with RangeIfAttribute
public class CandidateViewModel : ViewModelBase
{
private int _yearsOfExperience;
private bool _isExperienced;
[RangeIf(1, 100, "IsExperienced", ErrorMessage = "Years Of Experience must be greater than 0")]
public int YearsOfExperience
{
get
{
return this._yearsOfExperience;
}
set
{
this._yearsOfExperience = value;
}
}
public bool IsExperienced
{
get
{
return this._isExperienced;
}
set
{
this._isExperienced = value;
this.OnPropertyChanged("YearsOfExperience");
}
}
}
ViewModelBase class implementing IDataErrorInfo. If the validation attribute is a conditional attribute then i call GetValidationResult passing the viewmodel instance as validation context argument.
public class ViewModelBase : IDataErrorInfo, INotifyPropertyChanged
{
private readonly Dictionary<string, Func<ViewModelBase, object>> propertyGetters;
private readonly Dictionary<string, ValidationAttribute[]> validators;
public ViewModelBase()
{
this.validators = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValidations(p));
this.propertyGetters = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValueGetter(p));
}
public string this[string propertyName]
{
get
{
if (this.propertyGetters.ContainsKey(propertyName))
{
var errorMessages = this.validators[propertyName]
.Where(attribute => !this.Validate(attribute, propertyName))
.Select(attribute => attribute.ErrorMessage).ToList();
return string.Join(Environment.NewLine, errorMessages);
}
return string.Empty;
}
}
private bool Validate(ValidationAttribute validationAttribute, string propertyName)
{
var propertyValue = this.propertyGetters[propertyName](this);
if (IsConditionalValidationAttribute(validationAttribute))
{
return validationAttribute.GetValidationResult(propertyValue, new ValidationContext(this)) == ValidationResult.Success;
}
return validationAttribute.IsValid(propertyValue);
}
private bool IsConditionalValidationAttribute(ValidationAttribute validationAttribute)
{
return validationAttribute.GetType().GetCustomAttributes().Any(x => x.GetType() == typeof(ConditionalAttribute));
}
}
There are several ways in which conditional validation is handled in Wpf like using data triggers to disable error template but i find this a clean solution. Any comments are appreciated!
References
https://forums.asp.net/t/1924941.aspx?Conditional+Validation+using+DataAnnotation
History
16-12-2016
Original Article