WPF Validation with Attributes and IDataErrorInfo interface in MVVM

WPF provides validation infrastructure for binding scenarios through IDataErrorInfo interface. Basically you have to implement the Item[columnName] property putting the validation logic for each property in your Model (or ModelView) requiring validation. From XAML you need to set ValidatesOnDataErrors to true and decide when you want the binding invoke the validation logic (through UpdateSourceTrigger).

The idea of this post is try to generalize the validation logic in IDataErrorInfo.Item[] using the validation attributes in System.ComponentModel.DataAnnotations assembly.

First, we decorate each property needing validation with one or more validation attributes (An explanation of each validation attribute is out of the scope of this post) depending on the validation rules. For example if we have a property named Range we can add RangeAttribute to validate that the property value is between the boundaries:

[Required(ErrorMessage = "Field 'Range' is required.")]

[Range(1, 10, ErrorMessage = "Field 'Range' is out of range.")]

public int Range

{

    get

    {

        return this.range;

    }

    set

    {

        if (this.range != value)

        {

            this.range = value;

            this.OnPropertyChanged("Range");

        }

    }

}

Now, we need a few code to get the validation attributes and the value getters for each property requiring validation, just a couple of simple LINQ queries:

private static readonly Dictionary<string, Func<ValidationModelView, object>>

    propertyGetters = typeof(ValidationModelView).GetProperties()

                      .Where(p => GetValidations(p).Length != 0)

                      .ToDictionary(p => p.Name, p => GetValueGetter(p));

 

private static readonly Dictionary<string, ValidationAttribute[]> validators =

    typeof(ValidationModelView).GetProperties()

    .Where(p => GetValidations(p).Length != 0)

    .ToDictionary(p => p.Name, p => GetValidations(p));

 

private static ValidationAttribute[] GetValidations(PropertyInfo property)

{

    return (ValidationAttribute[])property

        .GetCustomAttributes(typeof(ValidationAttribute), true);

}

 

private static Func<ValidationModelView, object> GetValueGetter(PropertyInfo property)

{

    var instance = Expression.Parameter(typeof(ValidationModelView), "i");

    var cast = Expression.TypeAs(

        Expression.Property(instance, property),

        typeof(object));

    return (Func<ValidationModelView, object>)Expression

        .Lambda(cast, instance).Compile();

}

It is important emphasize that in order to improve the performance in the validation we use static dictionaries and Lamda expressions to generate the value getters. The validation attributes are cached in other dictionary in order to improve the performance too.

Finally we need implement the Item[columnName] property which is the part of code that really do the validation. It is very simple, WPF send the name of the property in the columnName argument, then all you have to do is get the value of the property using the propertyGetters dictionary and test this value against the validation attributes in the validators dictionary.

public string this[string columnName]

{

    get

    {

        if (propertyGetters.ContainsKey(columnName))

        {

            var value = propertyGetters[columnName](this);

            var errors = validators[columnName].Where(v => !v.IsValid(value))

                .Select(v => v.ErrorMessage).ToArray();

            return string.Join(Environment.NewLine, errors);

        }

 

        return string.Empty;

    }

}

All of this returns the list of message errors that will be shown in the UI.

You can do download a complete sample here.

8 Comments

  • How can you bind the Validation.HasError to your ViewModel so you can determine if it is valid inside the ViewModel?

  • I don't think you can do that, but I believe that is not necessary. If you need the login error you can get it by the Error property.
    The conversion error basically never go to the ViewModel, but there is no problem there becouse the button is disabled.

  • Great article! &nbsp;I've actually modified your code a bit to be in a separate class and use a Generic so it's easily usable by any model class with properties &amp; data annotations (more or less just replacing ValidationModelView with T) and it's working great inside my project! &nbsp;I wouldn't have been able to do it without your source though, so much appreciated.

  • That's good one.

  • That's some hot code ! !
    Would love to see something equally cool for whole-object validation.

  • Nice post, now that IDataErrorInfo is available in SL4, I have made an abstract view model base class (for SL4) based on this technique that provides automatic validation for any validation attributes on its descendants.
    The main changes I made were to support inheritance. I had to make the dictionaries instance fields and make use of the Delegate class for GetValueGetter() as well as using DynamicInvoke invoke in a few places. Also, I had to use GetValidationResult instead of IsValid.

  • I'm really impressed. it's really clever what you are doing with dynamically building your anonymous methods to retrive the property values. Unfortunately we were looking to put this is in an abstract class we use for our view models and we had to loose the static variables and the populate our dictionaries in constuctor and instead of storing the Funcs in the property dictionary we stored the PropertyInfo object. We loose so performance in having to do that for each instance of the view model, but we generally only use one instance per view so the cost is small.

  • I've used this method ever since reading about it. I also added support for "Validation Event Handlers"; basically, allowing you to add/remove validation methods to a property on the fly, which are called when the IDataErrorInfo index accessor is called for that property.

    The problem I have now, is; I have a complex Model, whose property validation methods depend on other properties. So when one property is changed, it might effect the "validity" of another property.

    How do I force my Model (or ViewModel) to notify the View that another property has become invalidated, when only one property has changed?

Comments have been disabled for this content.