在ViewModel实体上使用DataAnnotation进行Prism IDataErrorInfo验证

时间:2010-09-17 21:10:39

标签: c# validation mvvm prism idataerrorinfo

我正在使用Prism MVVM框架在WPF中实现数据验证。我正在ViewModel中使用干净数据实体,它们被绑定到表示层。

 <TextBox Text="{Binding User.Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

我在基础ViewModel类中实现了IDataErrorInfo的通用实现,该类对我的Entity上的DataAnnotation属性运行验证(在本例中为User)。

问题在于,当绑定到实体时,WPF框架在实体上查找IDataErrorInfo而不是ViewModel,这是我希望此逻辑存在的地方。如果我用我的ViewModel中的属性包装我的实体,那么一切正常,但我不希望在ViewModel中使用实体。

有没有办法告诉WPF在ViewModel中查找IDataErrorInfo而不是绑定的子对象?

谢谢, 麦克

2 个答案:

答案 0 :(得分:8)

我选择的选项是在基类中显式实现IDataErrorInfo,该类由所有ViewModel和Entities扩展。这似乎是使用WPF解决问题的最佳折衷方案,并且至少将IDataErrorInfo的实现隐藏到调用者,因此它们至少看起来很干净。我公开了一个受保护的ValidateProperty,如果需要,可以在子类中覆盖任何自定义行为(例如Password / PasswordConfirmation场景)。

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


}

答案 1 :(得分:2)

当然,我不了解您的整个场景,但我相信使用ViewModel包装您的业务实体(或模型)是MVVM模式的重要组成部分,特别是如果您没有可绑定模型(您可以直接绑定的模型)。包装可以包括本场景中的错误管理信息或其他内容,例如自定义模型显示等。

也就是说,你可以看看Prism的v4.0 MVVM RI,它使用INotifyDataErrorInfo进行验证,并且应该提供有关验证方法的有趣见解。

我希望这会有所帮助。

谢谢, 达米安