Проверка Prism IDataErrorInfo с помощью DataAnnotation в объектах ViewModel - PullRequest
2 голосов
/ 18 сентября 2010

Я реализую проверку данных в WPF с использованием инфраструктуры Prism MVVM. Я использую чистые объекты данных в ViewModel, которые привязаны к уровню представления.

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

Я реализовал универсальную реализацию IDataErrorInfo в базовом классе ViewModel, который выполняет проверку по атрибутам DataAnnotation в моем объекте (в данном случае - пользователь).

Проблема в том, что при связывании с сущностью платформа WPF ищет IDataErrorInfo на сущности, а не ViewModel, где я хочу, чтобы существовала эта логика. Если я оберну свою сущность свойствами в моей ViewModel, то все будет работать, но я не хочу ставить под угрозу использование сущностей в ViewModel.

Есть ли способ указать WPF искать IDataErrorInfo в ViewModel, а не дочерний объект, который привязан?

Спасибо, Mike

Ответы [ 2 ]

8 голосов
/ 22 сентября 2010

Вариант, с которым я пошел, состоял в том, чтобы реализовать IDataErrorInfo явно в базовом классе, который расширяется всеми ViewModels и 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);
    }


}
2 голосов
/ 20 сентября 2010

Конечно, я не знаю всего вашего сценария, но я считаю, что перенос ваших бизнес-сущностей (или модели) с использованием ViewModel является важной частью шаблона MVVM, особенно если у вас нет привязываемой модели ( модель, к которой вы можете привязать напрямую). Обтекание может включать информацию об управлении ошибками, как в этом сценарии, или другие вещи, такие как настройка отображения модели и т. Д.

Тем не менее, вы можете взглянуть на Prism v4.0 MVVM RI, который использует INotifyDataErrorInfo для проверки и должен предоставить интересную информацию о подходах проверки.

Надеюсь, это поможет.

Спасибо, Damian

...