Как да се покаже съобщението за грешка за 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. Добавих само пояснения към Името засега, докато успея да го накарам да работи, след което ще добавя останалите пояснения. - person Jake; 02.06.2015

Някои .NET технологии извличат и обработват MetadataTypes, докато други не. Опитайте да регистрирате приятелския клас преди употреба:

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

Това позволява да се извличат атрибути, декларирани в приятелски класове по прозрачен начин, без да се налага да знаете за MetadataTypes в основния клас.

person jlvaquero    schedule 02.06.2015
comment
Къде да добавя това? Viewmodel или някъде другаде? Опитах както viewmodel, така и в конструктора в класа App и нито един не работи. - person Jake; 02.06.2015
comment
Не е, така че вероятно съм пропуснал нещо някъде. Имате ли връзка или нещо, на което мога да се позова? - person Jake; 02.06.2015

В крайна сметка поправих това сам, като се свързах със свойство в модела на изгледа, а не със самия модел, и добавих анотациите към модела на изглед, вместо да се опитвам да разширя и претрупам модела.

Като се замисля, вероятно първо трябваше да направя това....

person Jake    schedule 13.06.2015