Пользовательская проверка модели зависимых свойств с использованием аннотаций данных

С тех пор я использовал отличный FluentValidation для проверки моих классов моделей. В веб-приложениях я использую его совместно с плагином jquery.validate, чтобы выполнить проверку на стороне клиента. Один из недостатков заключается в том, что большая часть логики проверки повторяется на стороне клиента и больше не централизована в одном месте.

По этой причине я ищу альтернативу. Есть много примеров там, показывающих использование аннотаций данных для выполнения проверки модели. Это выглядит очень многообещающе. Одна вещь, которую я не мог узнать, - это проверить свойство, которое зависит от другого значения свойства.

Возьмем, например, следующую модель:

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }
    [Required]
    public DateTime? EndDate { get; set; }
}

Я хотел бы убедиться, что EndDate больше, чем StartDate. Я мог бы написать обычай атрибут валидации, расширяющий ValidationAttribute, чтобы выполнить пользовательскую логику проверки. К сожалению, я не мог найти способ получить пример модели:

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // value represents the property value on which this attribute is applied
        // but how to obtain the object instance to which this property belongs?
        return true;
    }
}

Я обнаружил, что CustomValidationAttribute, похоже, выполняет эту работу, потому что у нее есть это свойство ValidationContext, которое содержит проверяемый экземпляр объекта. К сожалению, этот атрибут был добавлен только в .NET 4.0. Поэтому мой вопрос: могу ли я достичь той же функциональности в .NET 3.5 SP1?


UPDATE:

Кажется, что FluentValidation уже поддерживает проверку и метаданные клиентов в ASP.NET MVC 2.

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

Ответ 1

MVC2 поставляется с образцом "PropertiesMustMatchAttribute", который показывает, как заставить DataAnnotations работать для вас, и он должен работать как в .NET 3.5, так и в .NET 4.0. Этот пример кода выглядит следующим образом:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";

    private readonly object _typeId = new object();

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
        : base(_defaultErrorMessage)
    {
        OriginalProperty = originalProperty;
        ConfirmProperty = confirmProperty;
    }

    public string ConfirmProperty
    {
        get;
        private set;
    }

    public string OriginalProperty
    {
        get;
        private set;
    }

    public override object TypeId
    {
        get
        {
            return _typeId;
        }
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
            OriginalProperty, ConfirmProperty);
    }

    public override bool IsValid(object value)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
        object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
        object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
        return Object.Equals(originalValue, confirmValue);
    }
}

Когда вы используете этот атрибут, а не помещаете его в свойство вашего класса модели, вы помещаете его в сам класс:

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

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

Брэд Уилсон имеет хороший пример в своем блоге, в котором также показано, как добавить часть проверки на стороне клиента, хотя я не уверен, что этот пример будет работать как в .NET 3.5, так и в .NET 4.0.

Ответ 2

У меня была эта проблема, и недавно я нашел свое решение: http://foolproof.codeplex.com/

Безопасное решение для примера выше:

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}

Ответ 3

Вместо свойстваMustMatch атрибута CompareAttribute, который может использоваться в MVC3. Согласно этой ссылке http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:

public class RegisterModel
{
    // skipped

    [Required]
    [ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }                       

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
    public string ConfirmPassword { get; set; }
}

CompareAttribute - это новый, очень полезный валидатор, который на самом деле не является часть System.ComponentModel.DataAnnotations, но был добавлен в System.Web.Mvc DLL от команды. в то время как не особенно хорошо названный (единственный сравнение, которое он делает, - это проверить равенство, поэтому, возможно, EqualTo будет более очевидным), легко видеть из использование, которое проверяет этот валидатор что значение одного свойства равно значение другого свойства. Ты можешь см. из кода, что атрибут принимает свойство строки, которое имя другого имущества, которое вы сравниваете. Классическое использование этого валидатора - это то, что мы используют его здесь: пароль подтверждение.

Ответ 4

Поскольку методы DataAnnotations.NET 3.5 не позволяют вам предоставлять фактический объект, проверенный или контекст проверки, вам нужно будет немного обмануть, чтобы выполнить это. Должен признаться, что я не знаком с ASP.NET MVC, поэтому не могу сказать, как это сделать в точности с MCV, но вы можете попробовать использовать статическое значение потока для передачи самого аргумента. Вот пример с чем-то, что может сработать.

Сначала создайте своего рода "область объектов", которая позволяет передавать объекты, не передавая их через стек вызовов:

public sealed class ContextScope : IDisposable 
{
    [ThreadStatic]
    private static object currentContext;

    public ContextScope(object context)
    {
        currentContext = context;
    }

    public static object CurrentContext
    {
        get { return context; }
    }

    public void Dispose()
    {
        currentContext = null;
    }
}

Затем создайте свой валидатор для использования ContextScope:

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
         Event e = (Event)ObjectContext.CurrentContext;

         // validate event here.
    }
}

И последнее, но не менее важное: убедитесь, что объект прошел через ContextScope:

Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}

Это полезно?

Ответ 5

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

[Required]
public DateTime? StartDate { get; set; }    
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }

Он работает как на сервере, так и на стороне клиента. Подробнее можно найти здесь.