Проверка данных. Часть 3 – Основы создания атрибутов

Отметив с помощью стандартных атрибутов часть свойств Модели, можно добиться простого контроля получаемых значений. Для остальных правил можно разработать свои реализации атрибутов. Чтобы понять принцип их создания, посмотрим на составляющие класса ValidationAttribute.

Принципы создания атрибутов проверки данных

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

Первые три свойства уже были использованы в прошлых частях:

public string ErrorMessage { get; set; }
public string ErrorMessageResourceName { get; set; }
public Type ErrorMessageResourceType { get; set; }

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

Указать текст сообщения можно одним из двух взаимоисключающих вариантов:

  1. Передать его непосредственное значение в свойство ErrorMessage.
  2. Указать строку из ресурсов с помощью ErrorMessageResourceName и ErrorMessageResourceType.

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

protected string ErrorMessageString { get; }

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

public virtual string FormatErrorMessage(string name);

В качестве параметра он получает имя свойства, для которого применен атрибут. Базовая реализация использует это значение для подстановки вместо элемента {0} в шаблоне сообщения об ошибке. Обратите внимание, что метод объявлен как виртуальный и в создаваемых атрибутах его поведение можно изменить.

Осталось выяснить главное: какой метод отвечает за проверку значения свойства? Это:

protected virtual ValidationResult IsValid(object value, ValidationContext validationContext);

В данный метод передаются два параметра:

  • value – исходное значение, приведенное к object. Реальный тип определяется свойством, для которого установлен атрибут.
  • validationContext – контекст проверки, содержащий дополнительную информацию.

Если с первым параметром все ясно, то на втором остановимся подробнее. С помощью ValidationContext можно получить следующую информацию:

  • DisplayName – отображаемое имя проверяемого свойства. Оно может быть использовано, например, при формировании сообщения об ошибке в качестве параметра метода FormatErrorMessage().
  • ObjectType – тип текущего класса Модели.
  • ObjectInstance – непосредственно текущий экземпляр Модели, который был получен от пользователя. Это позволяет учитывать значения других свойств в ходе проверки.

В случае успешной проверки метод IsValid() должен вернуть значение, определенное в статическом свойстве ValidationResult.Success. В противном случае – экземпляр класса ValidationResult с текстом, описывающим суть ошибки.

Итак, для создания атрибута проверки данных необходимо:

  1. создать класс и унаследовать его от ValidationAttribute;
  2. переопределить IsValid();
  3. по необходимости добавить новые свойства и подменить FormatErrorMessage().

Остается только добавить новый атрибут свойствам Модели и создать в ресурсах строки с сообщениями об ошибках.

В завершении этой части, отметим несколько важных моментов, касающиеся проектирования атрибутов:

  • Обязательные параметры необходимо запрашивать в конструкторе, а для остальных лучше создать дополнительные публичные свойства.
  • Необходимо учитывать, что в атрибут можно передать только типы и простые константы.
  • Стоит помнить о принципе единственной ответственности (Single responsibility principle, SRP). Каждый атрибут должен реализовывать только свое конкретное правило.

Рассмотрим небольшой пример. За обязательное наличие значения свойства отвечает [Required]. Поскольку это правило уже реализовано, то все остальные атрибуты, при получении null, должны возвращать ValidationResult.Success. Такой подход позволит избежать дублирования кода и повысит удобство его чтения (все ограничения будут указаны явно через соответствующие атрибуты). Кроме того, это обеспечит отсутствие скрытого поведения при проверке Модели.

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

Pingbacks and trackbacks (3)+

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