Атрибут [Equal] – проверка свойства на равенство значению

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

Атрибут [Required] в данном случае не подходит. Дело в том, что после типа checkbox всегда возвращает одно из значений: true или false. Поэтому для решения данной задачи разработаем атрибут.

Постараемся сделать данную реализацию максимально универсальной, насколько это возможно в данном случае. Поэтому будем оперировать значениями типа object. Одновременно это означает, что в качестве значения для сравнения может быть указана пустая ссылка (null). Что делать в этом случае?

Давайте рассмотрим что значит null. Получить данное значение можно только для незаполненного поля ввода. Таким образом, сравнение с null означает его блокировку. Такое поведение лучше задать через атрибуты соответствующего Контроллера. Поэтому, при попытке сравнения с null, будем выбрасывать исключение NullReferenceException.

Может возникнуть еще один вопрос: а если полученное от поля значение будет равно пустой ссылке? Если в этом этом случае выполнить сравнение с заданным значением, то оно заведомо будет неудачным. А значит такое поведение дублирует [Require]. Вполне логично разделить ответственности. Это также даст большую гибкость при определении ограничений. Поэтому будем считать сравнение с полученным от поля ввода null всегда успешным. Кстати, именно так ведут себя и другие атрибуты используемые в ASP.NET MVC, например [RegularExpression].

Начнем разработку. Создадим класс EqualAttribute, который является наследником ValidationAttribute. Ограничим область использования будущего атрибута с помощью [AttributeUsage]. Разрешим назначать его только для свойств и без повторного указания. Кроме того, при наследовании класса Модели атрибут будет также унаследован.

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

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

Перейдем к методу IsValid(). Для сравнения объектов воспользуемся методом Equal(). При неудачном результате создадим экземпляр ValidationResult с соответствующим текстом сообщения.

namespace Demo.Attributes.Validation
{
    using System;
    using System.ComponentModel.DataAnnotations;

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class EqualAttribute : ValidationAttribute
    {
        private readonly object _valueToCompare;

        public EqualAttribute(object valueToCompare)
            : base("The {0} must be the same as the {1}.")
        {
            this._valueToCompare = valueToCompare;
        }

        #region ValidationAttribute overrides

        public override string FormatErrorMessage(string name)
        {
            return string.Format(
                this.ErrorMessageString, name, this._valueToCompare);
        }

        protected override ValidationResult IsValid(
            object value, ValidationContext validationContext)
        {
            if (this._valueToCompare == null) {
                throw new NullReferenceException();
            }

            if (value == null) {
                return ValidationResult.Success;
            }

            if (value.Equals(this._valueToCompare)) {
                return ValidationResult.Success;
            }

            return new ValidationResult(
                this.FormatErrorMessage(validationContext.DisplayName));
        }

        #endregion
    }
}

Остается добавить новый атрибут к свойству класса:

namespace Demo.Models
{
    .........
    using Demo.Attributes.Validation;
    .........
    public class ExampleClass
    {
        [Equal(true)]
        public bool AgreementAccepted { get; set; }
    }
}

Добавить комментарий