Prism IDataErrorInfo validation with DataAnnotation on ViewModel Entities

[亡魂溺海] 提交于 2019-11-30 10:51:28

The option I went with was to implement IDataErrorInfo explicitly in a base class which is extended by all ViewModels and Entities. This seems the best compromise to get things ticking over with WPF, and at least keeps the implementation of IDataErrorInfo hidden to callers so they at least appear clean. I expose a protected ValidateProperty which can be overridden if necessary in subclasses for any custom behaviour (such as for Password/PasswordConfirmation scenario).

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


}

Of course, I don't know your entire scenario, but I believe that wrapping your business entities (or model) using the ViewModel is a great part of the MVVM pattern, specially if you don't have a bindable model (a model to which you can bind directly). The wrapping can include error management information as in this scenario or other things such as customizing model display, etc.

That said, you can take a look at Prism's v4.0 MVVM RI, which uses the INotifyDataErrorInfo for validation, and should provide interesting insight on validation approaches.

I hope this helps.

Thanks, Damian

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