Как отобразить сообщение об ошибке для DataAnnotations

Я потратил последний час или около того на поиск ответа на этот вопрос, но почти каждый результат либо в ASP.NET, либо говорит о бесполезных для меня подходах Code First.

В основном у меня есть объекты POCO инфраструктуры базы данных, которые я проверяю на использование IDataErrorInfo.

Теперь это работает нормально, за исключением того, что у меня есть индексатор длиной 200 строк с примерно 40 операторами if в нем. (Я серьезно.)

Что я делаю прямо сейчас, так это расширяю классы:

public partial class MyPocoObject : IDataErrorInfo
{
    public string Error
    {
        get { throw new NotImplementedException("IDataErrorInfo.Error"); }
    }

    public string this[string columnName]
    {
        get
        {
            string result = null;

            // There's about 40 if statements here...
        }
    }
}

Ясно, что это неправильно, поэтому вместо этого я пытаюсь использовать DataAnnotations.

Это то, что я понял до сих пор.

Я создал классы метаданных следующим образом:

[MetadataType(typeof(MyObjectMetaData))]
public partial class MyObject { }

public class MyObjectMetaData
{
    [Required(AllowEmptyStrings = false, ErrorMessage = "Forename is a required field.")]
    public string Forename;
}

Затем у меня есть элементы управления, объявленные как таковые:

<TextBox Text="{Binding SelectedObject.Forename, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>

Тогда у меня есть триггер в другом месте:

<Style TargetType="TextBox" BasedOn="{StaticResource Global}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Когда я делал это с IDataErrorInfo, при сбое проверки я получал красную рамку и всплывающую подсказку с сообщением об ошибке. Используя аннотации данных, я ничего не получаю.

Как мне это реализовать? Потому что 40 операторов if в одном методе — это безумие.

Обновление: я попробовал код, предложенный в ответах, который не работает, хотя я думаю, что сделал это неправильно.

public partial class Member : IDataErrorInfo
{
    // Error: The type 'Project.Client.Models.Member' already contains a definition for 'Forename'
    [Required(AllowEmptyStrings = false, ErrorMessage = "Forename is a required field.")]
    public string Forename;

    private readonly Dictionary<string, object> _values = new Dictionary<string, object>();

    public string Error
    {
        get { throw new NotImplementedException("IDataErrorInfo.Error"); }
    }

    public string this[string columnName]
    {
        get { return OnValidate(columnName); }
    }


    protected virtual string OnValidate(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        string error = string.Empty;
        // Error: Project.Client.Models.Member.GetValue<T>(string)' cannot be inferred from the usage. Try specifying the type arguments explicitly
        var value = GetValue(propertyName);
        var results = new List<System.ComponentModel.DataAnnotations.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;
    }

    protected T GetValue<T>(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        object value;
        if (!_values.TryGetValue(propertyName, out value))
        {
            value = default(T);
            _values.Add(propertyName, value);
        }

        return (T)value;
    }
}

Обновление 2: Код, указанный в статье о MVVM, кажется, работает, но сообщения об ошибках по-прежнему не отображаются, даже несмотря на то, что OnValidate срабатывает.

Хотя сейчас что-то не так... хотя содержимое текстового поля меняется, значение, возвращаемое GetValue, всегда одинаково, а для пустого объекта, не загруженного из моей базы данных, проверка вообще не срабатывает.


person Jake    schedule 02.06.2015    source источник


Ответы (3)


Кажется, вам нужно интегрировать проверку DataAnnotations вручную. Возможно, System.ComponentModel.DataAnnotations.Validator не использует MetadataType по умолчанию, но регистрация его в TypeDescriptor, как в моем предыдущем ответе, должна работать.

Статья Код

Я имею в виду, что вы должны реализовать свой метод проверки и использовать System.ComponentModel.DataAnnotations.Validator.

Взгляните на реализацию PropertyChangedNotification исходного кода, который я связал:

/// <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<System.ComponentModel.DataAnnotations.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;
        }
person jlvaquero    schedule 02.06.2015
comment
Что вы имеете в виду под рукой? Я все еще добавляю аннотации в разделяемый класс, поскольку, если я добавлю их в модели, я потеряю их, как только решение будет построено. - person Jake; 02.06.2015
comment
Я также пробовал эту статью, и она выдает исключение System.ArgumentException с жалобой на дату рождения, которую я еще даже не реализовал. Это дает мне Additional information: The value for property 'DateOfBirth' must be of type 'System.DateTime'. - person Jake; 02.06.2015
comment
Я пробовал код, который вы указали в своем ответе, но он выдает исключение для DateOfBirth, для которого я еще даже не добавил аннотации. Я пытаюсь заставить Forename работать, прежде чем реализовывать все остальное. - person Jake; 02.06.2015
comment
Также код из статьи не работает, если я не добавлю аргумент типа к var value = GetValue(propertyName);, он говорит The type arguments for method 'Project.Client.Models.Member.GetValue<T>(string)' cannot be inferred from the usage. Try specifying the type arguments explicitly. - person Jake; 02.06.2015
comment
Полный код из статьи у меня работает. Я думаю, это хорошее начало для понимания того, как использовать DataAnnotations с MVVM. Конечно, в вашем случае вам нужно использовать классы друзей для добавления аннотаций к вашим частичным классам, но я уверен, что если System.ComponentModel.DataAnnotations.Validator не использует MetadataTypes по умолчанию, вы можете зарегистрировать TypeDescriptor, используя мой предыдущий ответ. - person jlvaquero; 02.06.2015
comment
Тогда я попробую еще раз, но нужно ли мне указывать аргумент типа для GetValue? В противном случае он не компилируется, и независимо от того, какой аргумент я ему задал, он всегда падает на DateOfBirth. - person Jake; 02.06.2015
comment
Я все еще получаю сообщение об ошибке компилятора с просьбой явно указать аргументы типа. - person Jake; 02.06.2015
comment
Нет, но мой делает. На имя и фамилию вроде нормально а потом вылетает на DateOfBirth. Пока я добавил аннотации только к Forename, пока не заработаю, а затем добавлю остальные аннотации. - person Jake; 02.06.2015

Некоторые технологии .NET извлекают и обрабатывают типы метаданных, а другие нет. Попробуйте зарегистрировать класс друзей перед использованием:

var myObjectMetadaProviderProvider = new AssociatedMetadataTypeTypeDescriptionProvider(typeof(MyObject), typeof(MyObjectMetaData));
TypeDescriptor.AddProvider(myObjectMetadaProviderProvider , typeof(MyObject));

Это позволяет прозрачно извлекать атрибуты, объявленные в классах друзей, без необходимости знать о типах метаданных в основном классе.

person jlvaquero    schedule 02.06.2015
comment
Где я могу добавить это? Модель представления или где-то еще? Я пробовал и модель представления, и конструктор в классе App, и ни один из них не работал. - person Jake; 02.06.2015
comment
Это не так, так что я, вероятно, что-то пропустил где-то. У вас есть ссылка или что-то, на что я могу сослаться? - person Jake; 02.06.2015

В итоге я исправил это сам, привязав свойство в модели представления, а не к самой модели, и добавив аннотации к модели представления, а не пытаясь расширить и загромождать модель.

Подумав об этом, я, вероятно, должен был сделать это в первую очередь....

person Jake    schedule 13.06.2015