При разработке веб-приложений достаточно часто приходится сталкиваться с ситуацией, когда необходимо проверить поле на равенство заданному значению. Очень распространённый пример – подтверждение согласия с условиями использования сайта.
Атрибут [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; }
}
}