I am writing a WPF application and I want to use Data Annotations to specify things like Required
Fields, Range
, etc.
My ViewModel classes use the regular INotifyPropertyChanged
interface and I can validate the entire object easily enough using the C# 4 Validator
, but I would also like the fields to highlight red if they do not validate properly. I found this blog post here (http://blogs.microsoft.co.il/blogs/tomershamam/archive/2010/10/28/wpf-data-validation-using-net-data-annotations-part-ii.aspx) that talks about how to write your base view model to implement IDataErrorInfo
and simply use the Validator, but the implementation doesn't actually compile nor can I see how it would work. The method in question is this:
/// <summary>
/// Validates current instance properties using Data Annotations.
/// </summary>
/// <param name="propertyName">This instance property to validate.</param>
/// <returns>Relevant error string on validation failure or <see cref="System.String.Empty"/> on validation success.</returns>
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("Invalid property name", propertyName);
}
string error = string.Empty;
var value = GetValue(propertyName);
var results = new List<ValidationResult>(1);
var result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
The problem is GetValue
is not provided. He could be talking about the GetValue
that comes when you inherit DependencyObject
, but the syntax still doesn't work (it expects you to pass DependencyProperty
as a parameter) but I'm using regular CLR properties with OnPropertyChanged("MyProperty")
being invoked on the setter.
Is there a good way to connect the validation to the IDataErrorInfo
interface?
Using your above code as a starting point I got this working through IDataErrorInfo.
Your problem centred around getting the value of the property when you only have the property name, reflection can help here.
public string this[string property]
{
get
{
PropertyInfo propertyInfo = this.GetType().GetProperty(property);
var results = new List<ValidationResult>();
var result = Validator.TryValidateProperty(
propertyInfo.GetValue(this, null),
new ValidationContext(this, null, null)
{
MemberName = property
},
results);
if (!result)
{
var validationResult = results.First();
return validationResult.ErrorMessage;
}
return string.Empty;
}
}
I know this post is old, but I recently solved this problem with help from this post, while making some optimizations along the way. I'd like to share my ViewModelBase's implementation of IDataErrorInfo. It uses compiled expressions for the property getters which speeds the property value access. I also fire off the expression compilations on background thread when the type is loaded into memory. Hopefully, it finishes compilation before the first call to OnValidate since expression compilation can be a bit slow. Thanks and cheers.
public abstract class ViewModelBase<TViewModel> : IDataErrorInfo
where TViewModel : ViewModelBase<TViewModel>
{
string IDataErrorInfo.Error
{
get { throw new NotSupportedException("IDataErrorInfo.Error is not supported, use IDataErrorInfo.this[propertyName] instead."); }
}
string IDataErrorInfo.this[string propertyName]
{
get { return OnValidate(propertyName, propertyGetters.Result[propertyName]((TViewModel)this)); }
}
private static Task<Dictionary<string, Func<TViewModel, object>>> propertyGetters = Task.Run(() =>
{
return typeof(TViewModel).GetProperties()
.Select(propertyInfo =>
{
var viewModel = Expression.Parameter(typeof(TViewModel));
var property = Expression.Property(viewModel, propertyInfo);
var castToObject = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda(castToObject, viewModel);
return new
{
Key = propertyInfo.Name,
Value = (Func<TViewModel, object>)lambda.Compile()
};
})
.ToDictionary(pair => pair.Key, pair => pair.Value);
});
protected virtual string OnValidate(string propertyName, object propertyValue)
{
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName };
if (!Validator.TryValidateProperty(propertyValue, validationContext, validationResults))
{
return validationResults.First().ErrorMessage;
}
return string.Empty;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With