Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Conditional Validation in WPF using Attributes

0.00/5 (No votes)
16 Dec 2016 1  
Extending Data Annotations library to support conditional validation in WPF

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"); /*Property Changed callback should be fired for
                                                           the dependent property '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 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here