Как получить метаданные модели из атрибута ValidationAttribute? - PullRequest
4 голосов
/ 02 марта 2010

MVC2 поставляется с хорошим примером атрибута проверки с именем «PropertiesMustMatchAttribute», который сравнивает два поля, чтобы определить, совпадают ли они. Использование этого атрибута выглядит следующим образом:

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
    public string NewPassword { get; set; }
    public string ConfirmPassword { get; set; }
}

Атрибут прикреплен к классу модели и использует немного отражения для своей работы. Вы также заметите, что сообщение об ошибке здесь указывается напрямую: «Новый пароль и пароль подтверждения не совпадают».

Если вы не укажете сообщение, сообщение по умолчанию будет сгенерировано с использованием следующего кода (сокращенно для ясности):

private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
public override string FormatErrorMessage(string name)
{
    return String.Format(CultureInfo.CurrentUICulture, _defaultErrorMessage,
        OriginalProperty, ConfirmProperty);
}

Проблема в том, что «OriginalProperty» и «ConfirmProperty» являются жестко закодированными строками в атрибуте - «NewPassword» и «ConfirmPassword» в этом примере. На самом деле они не получают реальные метаданные модели (например, DisplayNameAttribute), чтобы собрать более гибкое, локализуемое сообщение. Я хотел бы иметь более общеприменимый атрибут сравнения, который использует информацию отображаемого имени метаданных и т. Д., Которое было указано.

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

Как получить ссылку на метаданные модели для модели, которую я проверяю, из атрибута?

(Хотя я нашел несколько вопросов о том, как проверить зависимые поля в модели, ни один из ответов не включает в себя правильную обработку сообщения об ошибке.)

1 Ответ

4 голосов
/ 02 марта 2010

На самом деле это подмножество вопроса как получить экземпляр, оформленный атрибутом, из атрибута (аналогичный вопрос здесь ).

К сожалению, короткий ответ: вы не можете.

Атрибуты являются метаданными. Атрибут не знает и не может знать что-либо о классе или члене, который он украшает. Кто-то из нижестоящих потребителей этого класса должен найти указанные пользовательские атрибуты и решить, следует ли / когда / как их применять.

Вы должны думать об атрибутах как данные , а не объекты . Хотя атрибуты технически являются классами, они довольно глупы, потому что имеют критическое ограничение: все в них должно быть определено во время компиляции. Это фактически означает, что они не могут получить доступ к какой-либо информации времени выполнения, если не предоставят метод, который принимает экземпляр времени выполнения и , когда вызывающая сторона решит вызвать его.

Вы могли бы сделать последнее. Вы можете создать свой собственный атрибут, и, пока вы контролируете валидатор, вы можете заставить валидатор вызывать какой-либо метод для атрибута и заставлять его делать что угодно:

public abstract class CustomValidationAttribute : Attribute
{
    // Returns the error message, if any
    public abstract string Validate(object instance);
}

Пока тот, кто использует этот класс, правильно использует атрибут, это будет работать:

public class MyValidator
{
    public IEnumerable<string> Validate(object instance)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        Type t = instance.GetType();
        var validationAttributes = (CustomValidationAttribute[])Attribute
            .GetCustomAttributes(t, typeof(CustomValidationAttribute));
        foreach (var validationAttribute in validationAttributes)
        {
            string error = validationAttribute.Validate(instance);
            if (!string.IsNullOrEmpty(error))
                yield return error;
        }
    }
}

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

public class PasswordValidationAttribute : CustomValidationAttribute
{
    public override string Validate(object instance)
    {
        ChangePasswordModel model = instance as ChangePasswordModel;
        if (model == null)
            return null;
        if (model.NewPassword != model.ConfirmPassword)
            return Resources.GetLocalized("PasswordsDoNotMatch");
        return null;
    }
}

Это все хорошо и просто, просто поток управления инвертирован по сравнению с тем, который вы указали в исходном вопросе. Атрибут ничего не знает о том, к чему он был применен; валидатор, который использует , атрибут должен предоставить эту информацию (что он может легко сделать).

Конечно, это не то, как валидация на самом деле работает с аннотациями данных в MVC 2 (если только она не сильно изменилась со времени моего последнего просмотра). Я не думаю, что вы сможете просто подключить это с ValidationMessageFor и другими подобными функциями. Но в MVC 1 мы все равно должны были написать все наши валидаторы. Вам ничто не помешает объединить DataAnnotations с вашими собственными атрибутами проверки и валидаторами, просто потребуется немного больше кода. Вам придется вызывать ваш специальный валидатор везде, где вы пишете код валидации.

Возможно, это не тот ответ, который вы ищете, но, к сожалению, так оно и есть; атрибут валидации не может знать о классе, к которому он был применен, если только эта информация не предоставлена ​​самим валидатором.

...