Model Validation with Data Annotations and Metadata Classes in WPF

In the ASP.NET MVC world, Microsoft has provided an interesting technique for data validation: data annotations. Data annotations are lightweight, and, using Metadata classes (shown in the previous link), data annotations can be applied effortlessly to entity framework or datatset generated models (for database-first uses).  This technique has been very convenient for our EF/MVC applications.

What about WPF applications?

Suddenly the waters get murky.  When using data binding with WPF, supported validation techniques include:

None of these fit well with data annotations.  However, there is a way forward: the blog post “Data Validation in WPF” includes a bit on implementing data annotations with the help of a validator class.  Unfortunately, this approach does not support metadata classes!

To work with generated data classes and metadata model validation in WPF, I have extended the above exchange to support validation rules in metadata classes.  In this way, you can share your metadata validation rule classes between WPF and MVC applications with no changes.

Implementation

For the model class that you want to provide data annotation support to, extend it with a partial class implementing INotifyDataErrorInfo.  The implementation is completely boilerplate, and you could extend your code generator template to include it. For this example, my model class is called “Bridges”, as it is directly derived from the table of that name in the database. I’ll assume we’re going to add a metadata class called “BridgesValidation” to validate some aspects of this model.

To begin with, we can create a partial class for the model, adding an interface for INotifyDataErrorInfo and attaching the metadata class in the usual way (the same way you would with EF database first).

[MetadataType(typeof(BridgesValidation))]
public partial class Bridges :  INotifyDataErrorInfo

Next, we need to provide the interface event, and prepare a dictionary for storing which fields are in error. The method will simply lookup in the dictionary to see if the requested property is in error.

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private Dictionary<string, string> errorsForName = new Dictionary<string, string>();

public System.Collections.IEnumerable GetErrors(string propertyName)
{
   if (errorsForName.ContainsKey(propertyName))
      return new string[] { errorsForName[propertyName] };
   else
      return new string[0];
}

How (and when?) to calculate which fields are in error? I created a calculate errors method, which I attached to the column changed event notification for the model (which derives from DataRow). You could just as easily attach to the property changed event notification for the model. Depending on how the model is implemented (DataRow, EF, etc) the exact attachment procedure will vary. In any case, however, a boilerplate error calculation routine can be used.

The process for this routine is:

  1. Clear all existing errors
  2. Acquire all metadata class types
  3. For each property and each metadata class, find any validation attributes
  4. Apply validation attributes and save errors

First, clear all existing errors. If deriving from DataRow, could also call “ClearErrors” here.

errorsForName.Clear();

Second, acquire all metadata class types (this is the interesting bit that the above link does not include).

var types = this.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), false)
                          .OfType<MetadataTypeAttribute>()
                          .Select(mta => mta.MetadataClassType);

Finally, look through all classes and all properties to find any validation attributes. When the “Add” is called on errorsForName, if using a DataRow derivative, that would be a good time to call “SetColumnError” as well.

        foreach (Type t in types.Union(new Type[] { this.GetType() }))
        {
            foreach (var prop in t.GetProperties())
            {
                var validations = prop.GetCustomAttributes(true).OfType<ValidationAttribute>().ToArray();
                foreach (var val in validations)
                {
                    var propVal = this[prop.Name];
                    if (!val.IsValid(propVal))
                    {
                        var errMsg = val.FormatErrorMessage(prop.Name);
                        errorsForName.Add(prop.Name, errMsg);
                        if (ErrorsChanged != null)
                            ErrorsChanged(this, new DataErrorsChangedEventArgs(prop.Name));
                    }
                }
            }
        }

Now you are ready to use data annotation validations in WPF!

Simply use the same kind of data annotation metadata class as you would for an ASP.NET MVC application, and use the above partial class template to extend each generated model class file. WPF will pick up the INotifyDataErrorInfo, which will in turn provide a seamless link between the metadata class data annotations and the WPF user interface.

Comments are closed.