Click here to Skip to main content
15,884,298 members
Articles / Desktop Programming / WPF

Conditional Validation in WPF using Attributes

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
16 Dec 2016CPOL 11.2K   165   2  
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.

Image 1

In the following gif the validation is fired only when the check box (Experienced condition) is true.

Image 2

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.

C#
[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

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Bally Technologies
India India
I am a full stack developer working in .net technologies.

Comments and Discussions

 
-- There are no messages in this forum --