Проверка данных. Часть 2 – Стандартные атрибуты

Перейдем к назначению атрибутов свойствам Модели. И перед тем как начать создавать их свои реализации, посмотрим какие готовые варианты уже существуют в .NET.

Стандартные атрибуты ASP.NET MVC 3

Раз есть стандартный для .NET механизм проверки данных, то должны быть и атрибуты для наиболее распространённых сценариев. И это утверждение верно. Часть из них расположена в пространстве имен System.Web.Mvc. Кроме того, начиная с 3 версии ASP.NET MVC поддерживает достаточно большое число атрибутов из уже существовавшего System.ComponentModel.DataAnnotations.

Но вначале необходимо отметить важный момент: атрибуты могут получать в качестве параметров только простые константы или типы. Поэтому ему невозможно передать, например, текущую дату как вызов метода DateTime.Now.ToString(). В такой ситуации придется создавать свою реализацию атрибута. При это она может создавать экземпляр объекта самостоятельно или получать его тип как параметр. Последний вариант удобен в том случае, если у заданного класса есть наследники. Тогда конкретный тип можно уточнить при установке атрибута.

[Compare]

Предназначен для сравнения текущих значений двух свойств. Данный атрибут появился в 3 версии ASP.NET MVC и расположен в пространстве имен System.Web.Mvc. Хорошим примером его использования может послужить класс, представляющий форму для регистрации пользователя. В нем есть пароль Password и его подтверждение ConfirmPassword:

public string Password { get; set; }

[Compare("Password")]
public string ConfirmPassword { get; set; }

[DataType]

Применяется для уточнения типа, используя варианты из перечисления DataType. Можно сказать, что при этом определяется физический смысл свойства. Стоит учесть, что атрибут не осуществляет непосредственной проверки данных на соответствие выбранному уточнению.

Несмотря на кажущуюся необязательность, уточнение типов достаточно важный момент в описании свойств Модели в ASP.NET MVC. Причина этого заключена в том, что на него опираются многие вспомогательные методы движка представлений.

Например, до сих пор, при вводе паролей, набранные символы отображались на экране. Дело в том, что отображение их полей ввода осуществляется вспомогательным методом EditorFor() класса HtmlHelper. Он подбирает наиболее оптимальный HTML элемент для каждого свойства исходя из его типа. Чтобы вместо обычного поля ввода был выведен вариант со скрытыми символами, необходимо задать уточнение:

[DataType(DataType.Password)]
public string Password { get; set; }

Аналогично можно задать использование HTML элемента textarea, указав DataType.MultilineText:

[DataType(DataType.MultilineText)]
public string Description { get; set; }

Рассмотрим еще один пример и отметим свойство Email следующим образом:

[DataType(DataType.EmailAddress)]
public string Email { get; set; }

В этом случае, другой вспомогательный метод DisplayFor() класса HtmlHelper вместо простой строки выведет гиперссылку на содержащийся в данном свойстве адрес электронной почты.

[EnumDataType]

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

public enum ValidAreaCodes { 
    v1 = 1, v2 = 3, v3 = 7, v4 = 8, v5 = 34, v6 = 42 
}

public class MapModel
{
    [EnumDataType(typeof(ValidAreaCodes))]
    public int AreaCode { get; set; }
} 

В данном случае, свойство AreaCode сможет получить только значения 1, 3, 7, 8, 34 и 42.

[DisplayFormat]

Определяет формат вывода значения. Позволяет указать нужно ли конвертировать пустую строку в значение null. А также задает текст для отображения, если значение самого объекта равно null.

[HiddenInput]

Указывает на необходимость вывода свойства с использованием скрытого поля. По умолчанию отменяет вывод дополнительных HTML блоков, таких как label. Реализация расположена в System.Web.Mvc.

[Range]

Определяет диапазон допустимых значений, который указывается в качестве параметров. Возможно указание диапазонов типа Int32 и Double. Кроме того, можно использовать любой тип, который поддерживает интерфейс IComparable для реализации операции сравнения.

Для примера, посмотрим на объявление свойства Rating, которое должно содержать оценку от 1 до 5:

[Range(1, 5)]
public int Rating { get; set; }

При проверке значение свойства приводится к типу параметров атрибута. Например:

[Range(1, 5)]
public string Rating { get; set; }

В этом случае строка будет приведена к типу Int32. Полученное число будет сопоставлено с заданным диапазоном. Таким образом, проверку пройдут только строки, представляющие целые числа от 1 до 5 включительно.

Рассмотрим вариант использования атрибута с произвольным типом. Для примера возьмем тип DataTime и установим диапазон допустимых дат с 1 января 2000 года по 1 января 2012 года.

[Range(typeof(DateTime), "01-01-2000", "01-01-2012")]
public DateTime PublishedAt { get; set; }

[ReadOnly]

Отмечает свойство, как разрешенное только для чтения (отображения).

[RegularExpression]

Позволяет задать регулярное выражение, которое описывает возможные значения выбранного свойства. Не заменяет [Required], т.к. при отсутствии значения проверка не производится.

С помощью этого атрибута легко сопоставить текстовое значения с заданным форматом. В качестве достаточно показательного примера можно рассмотреть свойства Email и Homepage:

[RegularExpression(@"[\w\.-]*[a-zA-Z0-9_]@[\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]")]
public string Email { get; set; }

[RegularExpression(@"(http(s)?://)?([\w-]+\.)+[\w-]+(/[\w- ;,./?%&=]*)?")]
public string Homepage { get; set; }

Поскольку атрибут может использоваться с любимыми типами, то перед сравнением они будут переведены в строковое представление. Например, вот так можно ограничить числовые значения определенным списком (зададим его аналогичным списку из примере атрибута [EnumDataType]):

[RegularExpression(@"(1)|(3)|(7)|(8)|(34)|(42)")]
public int AreaCode { get; set; }

В собственных классах для данной цели необходимо переопределить метод ToString().

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

[Required]

Указывает на то, что отмеченное свойство обязательно должно содержать значение. Среди его свойств самого атрибута можно отметить AllowEmptyStrings, определяющее считается ли пустая строка также недопустимым значением. Пример использования:

[Required]
public string Login { get; set; }

[ScaffoldColumn]

Указывает что свойство может выводиться как в виде значения (например в таблицах и списках), так и в формах в качестве поля для ввода данных.

[StringLength]

Очень простой, но иногда полезный атрибут. Позволяет указать максимальную длину введенной пользователем строки. Например, ограничим свойство DisplayName двадцатью символами:

[StringLength(20)]
public string DisplayName { get; set; }

[UIHint]

Указывает Представлению использовать заданный шаблон для вывода значения.

[CustomValidation]

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

Разберем использование [CustomValidation] на примере свойства IsVisible класса BookDetails. Обратите внимание, что все связанные с этим изменения в демонстрационном проекте отражены не будут. Для простоты их отмены можно сохранить копию файла BookDetails.cs.

Поставим условие, что все добавляемые книги должны быть отмечены как отображаемые в каталоге. Атрибут [Require] в данном случае не подходит, так как HTML-элемент checkbox всегда будет возвращать значение. Поэтому для решения данной задачи используем [CustomValidation].

Для простоты, непосредственно в файле BookDetails.cs, создадим статический класс BookDetailsValidator. Добавим в него статический метод IsVisibleValidator(), ответственный за алгоритм проверки. В качестве параметров он будет принимать:

  • value – значение для проверки, того же типа что и проверяемое свойство;
  • context – переменная типа ValidationContext, содержащая контекст текущего запроса. В частности: отображаемое имя свойства и ссылку на экземпляр объекта, которому оно принадлежит.

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

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

Перейдем к исходному коду. Для краткости была убрана часть класса BookDetails и явно задан текст строки сообщения об ошибке вместо обращения к ресурсу:

public class BookDetails
{
    .........

    [CustomValidation(typeof(BookDetailsValidator), "IsVisibleValidator")]
    public bool IsVisible { get; set; }
}

public static class BookDetailsValidator
{
    public static ValidationResult IsVisibleValidator(
        bool value, ValidationContext context)
    {
        if (value) { return ValidationResult.Success; }

        return new ValidationResult("New books must be visible in catalog.");
    }
}

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

Уберем изменения, сделанные для демонстрации использования [CustomValidation].

Теперь, ознакомившись с доступными в .NET атрибутами, в следующей части можно указать подходящие для свойств классов Модели.

Комментарии (10) -

Валидация [DataType(DataType.EmailAddress)] в mvc 3 не работает. Говорят еще соответствующие функции не включили. Пришлось добавить регулярку.

DataType не является атрибутом проверки. Он уточняет тип свойства, которому присвоен. Например, при установке [DataType(DataType.EmailAddress)], значение будет выведено как гиперссылка на "mailto:адрес-из-свойства".

Действительно. Проглядел этот факт, спасибо!

Артем 04.08.2012 12:08:26

[EnumDataType]... как то не в курсе, но нет ли возможности использовать просто перечисление? работает ли биндинг для перечислений?

пример:

        public enum ValidAreaCodes {
      v1 = 1, v2 = 3, v3 = 7, v4 = 8, v5 = 34, v6 = 42
  }
  
  public class MapModel
  {
      public ValidAreaCodes AreaCode { get; set; }
  }

@ Артем: Насколько мне известно - нет. Enum трактуется как числовое значение. В стандартных заготовках страниц для свойства такого типа поле ввода не создается. @Html.EditorFor создает обычный textbox, в который можно вводить что угодно.

В принципе накидать атрибут для валидации Enum не так и сложно. Но из коробки такого нет.

@ Andrey:
Поставил тип DataType.Date, и теперь он требует вводить дату только в виде dd/mm/yyyy хотя для даты вполне съедобны и разделители в виде точек. Можно как-нибудь поправить это в настройках? Если да, то где?

@ Andrey:

@ Роман:

stackoverflow.com/.../mvc4-datatype-date-editorfor-wont-display-date-value-in-chrome-fine-in-ie

Нашел как лечится, в другую сторону можно все отображать с разделителями '/', хотя конечно вопрос еще актуален

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

Эдвин 27.11.2015 20:14:37

А свойство Range для даты, как поставить текущую дату максимум?

Эдвин Тут придется написать свой аттрибут.

Pingbacks and trackbacks (3)+

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