Combining DataAnnotations and IDataErrorInfo for WPF

社会主义新天地 提交于 2019-12-07 05:23:53

问题


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?


回答1:


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;
   }
}



回答2:


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;
    }
}


来源:https://stackoverflow.com/questions/7071595/combining-dataannotations-and-idataerrorinfo-for-wpf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!