Cross-property validation in WPF MvvM

By Mirek on (tags: CustomValidationAttribute, mvvm, validation, WPF, categories: code)

In a previous post I have described hot to utilize a INotifyDataErrorInfo interface. Today I will show you my solution for automated cross-property validation integrated with the ModelValidation base class from previous post.

First I add a custom CrossValidationAttribute

   1: public abstract class ModelValidation : NotifyModelBase, INotifyDataErrorInfo
   2: {
   3:     /// <summary>
   4:     /// Allows to cross validate model properties. Can be applied only in ModelValidation derived classes.
   5:     /// Model should override the OnPropertyValidate method to provide custom cross validation
   6:     /// </summary>
   7:     public class CrossValidationAttribute : ValidationAttribute
   8:     {
   9:         protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  10:         {
  11:             ModelValidation model = validationContext.ObjectInstance as ModelValidation;
  12:             if (model == null)
  13:                 throw new ApplicationException("CrossValidationAttribute can be used only in classes that inherit from ModelValidation");
  14:  
  15:             string propertyName = validationContext.MemberName;
  16:             if (string.IsNullOrEmpty(propertyName))
  17:                 throw new ApplicationException("CrossValidationAttribute requires valid property name");
  18:  
  19:             //call overriden validation method on model
  20:             var result = model.OnPropertyValidate(propertyName, value);
  21:             if (string.IsNullOrEmpty(result))
  22:                 return ValidationResult.Success;
  23:             else
  24:                 return new ValidationResult(result, new[] { propertyName });
  25:         }
  26:     }

and later on a virtual method

   1: /// <summary>
   2: /// Override this method to use custom cross validation for properties decorated with CrossValidationAttribute
   3: /// </summary>
   4: /// <param name="propertyName">the validated property name</param>
   5: /// <param name="value">current property value</param>
   6: /// <returns>Error message or null</returns>
   7: protected virtual string OnPropertyValidate(string propertyName, object value)
   8: {
   9:     return null;
  10: }

which will be called from above CrossValidationAttribute for each property marked with this attribute. Since this attribute is strictly connected with ModelValidation class I’ve put it inside this class and add a check (lines 11-13) if the context model is ModelValidation derived object.

Now our model looks like this

 
public class MainWindowModel : ModelValidation
{
    [...]
 
    [Phone]
    [CrossValidation]
    public string Phone
    {
        get { return _phone; }
        set
        {
            _phone = value;
            NotifyAndValidateProperty(value);
        }
    }
 
    protected override string OnPropertyValidate(string propertyName, object value)
    {
        if (propertyName == "Phone")
        {
            if (Name == null)
                return "Provide name first";
        }
        return null;
    }
 
    [...]
}
All we need to make it work is to override OnPropertyValidate method and mark properties with CrossValidationAttribute.