Validating Prism IDataErrorInfo with DataAnnotation for ViewModel Objects
I am doing data validation in WPF using Prism MVVM framework. I am using pure data Entities in the ViewModel that are bound to the view layer.
<TextBox Text="{Binding User.Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
I have implemented a generic implementation of IDataErrorInfo in a base ViewModel class that triggers validation for DataAnnotation attributes on my Entity (User in this case).
The problem is that when binding to an Entity, the WPF framework looks for the IDataErrorInfo for the Entity, not the ViewModel where I want this logic to exist. If I wrap my Entity with properties in my ViewModel then everything works, but I don't want to compromise on the use of Entities in the ViewModel.
Is there a way to tell WPF to look for IDataErrorInfo in the ViewModel and not the child that is bound?
Thanks, Mike
source to share
The option I went with was to explicitly implement IDataErrorInfo in a base class that extends across all ViewModels and Entities. This seems like the best compromise for getting things looping around WPF, and at least keeps the IDataErrorInfo implementation hidden to callers, so they at least seem clean. I am exposing the protected ValidateProperty, which can be overridden if needed in subclasses for any custom behavior (e.g. Password / PasswordConfirmation script).
public abstract class DataErrorInfo : IDataErrorInfo
{
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get { return ValidateProperty(columnName); }
}
protected virtual string ValidateProperty(string columnName)
{
// get cached property accessors
var propertyGetters = GetPropertyGetterLookups(GetType());
if (propertyGetters.ContainsKey(columnName))
{
// read value of given property
var value = propertyGetters[columnName](this);
// run validation
var results = new List<ValidationResult>();
var vc = new ValidationContext(this, null, null) { MemberName = columnName };
Validator.TryValidateProperty(value, vc, results);
// transpose results
var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
return string.Join(Environment.NewLine, errors);
}
return string.Empty;
}
private static readonly Dictionary<string, object> PropertyLookupCache =
new Dictionary<string, object>();
private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
{
var key = objType.FullName ?? "";
if (!PropertyLookupCache.ContainsKey(key))
{
var o = objType.GetProperties()
.Where(p => GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, CreatePropertyGetter);
PropertyLookupCache[key] = o;
return o;
}
return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
}
private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
{
var instanceParameter = Expression.Parameter(typeof(object), "instance");
var expression = Expression.Lambda<Func<object, object>>(
Expression.ConvertChecked(
Expression.MakeMemberAccess(
Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
propertyInfo),
typeof(object)),
instanceParameter);
var compiledExpression = expression.Compile();
return compiledExpression;
}
private static ValidationAttribute[] GetValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
}
source to share
Of course, I don't know your entire scenario, but I believe that transferring your business entities (or models) with a ViewModel is a big part of the MVVM pattern, especially if you don't have a compatible model (a model that you can link to directly) ... The wrapper can include error management information, as in this scenario, or other things like customizing the display of the model, etc.
However, you can take a look at Prism v4.0 MVVM RI which uses INotifyDataErrorInfo validation and should provide interesting information about validation approaches.
Hope this helps.
Thanks, Damian
source to share