Пользовательская проверка модели зависимых свойств с использованием аннотаций данных - PullRequest
43 голосов
/ 17 февраля 2010

С тех пор я использовал превосходную библиотеку 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?


ОБНОВЛЕНИЕ:

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

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

Ответы [ 5 ]

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

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.

14 голосов
/ 06 апреля 2010

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

Решением Foolproof для приведенного выше примера будет:

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

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}
7 голосов
/ 23 мая 2011

Вместо PropertiesMustMatch атрибут 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 голосов
/ 18 июля 2014

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

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

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

3 голосов
/ 17 февраля 2010

Поскольку методы 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);
}

Это полезно?

...