Часть 21 – Провайдеры правил (логики) проверки Модели

Как уже можно было убедиться, информация, необходимая для осуществления проверки данных, не входит в состав класса ModelMetadata. Давайте разберемся где она расположена и как её изменить.

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

  • Изначально параметры для проверки значений Модели могут быть распложены как в метаданных объекта, так и в любом другом хранилище. Таким образом, их указание может осуществляться как атрибутами, так и, например, с помощью конфигурационного файла.
  • После создания экземпляра ModelMetadata происходит обращение к провайдерам логики проверки Модели. Это наследники класса ModelValidatorProvider, предоставляющие коллекцию правил, экземпляров ModelValidator, для указанного объекта.
  • В дальнейшем, полученные правила ядро ASP.NET MVC будет использовать как для настройки проверки данных на стороне клиента, так и проверки значений Модели на сервере.

А теперь перейдем к классам, которые участвуют в данном процессе.

ModelValidator

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

public abstract IEnumerable<ModelValidationResult> Validate(Object container)

Результатом работы Validate() является коллекция экземпляров ModelValidationResult. Этот класс содержит два свойства: MemberName – имя свойства, для которого он создан, и Message – текст сообщения об ошибке. Если Модель корректна, то возвращается пустое перечисление.

Вот ещё один метод, который необходимо упомянуть при рассмотрении данного класса:

public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules();

Это список правил для проверки данных на стороне клиента. Их создание уже было рассмотрено при разработке атрибутов. Поэтому не будем подробно останавливаться на этом методе.

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

protected ModelValidator(ModelMetadata metadata, ControllerContext controllerContext);

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

Поддержка DataAnnotation

Практически все атрибуты проверки данных это наследники класса ValidationAttribute, т.е. являются атрибутами DataAnnotations. Поэтому разработчики ASP.NET MVC 3 не могли оставить их без особого внимания. Поэтому были созданы следующие классы, по сути являющиеся адаптерами:

public class DataAnnotationsModelValidator : ModelValidator
{
    public DataAnnotationsModelValidator(
        ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);

    protected internal ValidationAttribute Attribute { get; }

    protected internal string ErrorMessage { get; }

    public override bool IsRequired { get; }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules();

    public override IEnumerable<ModelValidationResult> Validate(object container);
}

DataAnnotationsModelValidator получает экземпляр ValidationAttribute в качестве параметра конструктора. Таким образом создаются ASP.NET MVC создает ModelValidator для любого атрибута проверки данных.

При необходимости, можно воспользоваться уточнением в виде generic-версии класса:

public class DataAnnotationsModelValidator<TAttribute> 
    : DataAnnotationsModelValidator 
    where TAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
{
    public DataAnnotationsModelValidator(
        ModelMetadata metadata, ControllerContext context, TAttribute attribute);

    protected TAttribute Attribute { get; }
}

Главное отличие в том, что свойство Attribute теперь указанного типа TAttribute.

Кроме того, существуют уже готовые реализации для стандартных атрибутов:

  • public class RangeAttributeAdapter : DataAnnotationsModelValidator<RangeAttribute>
  • public class RegularExpressionAttributeAdapter : DataAnnotationsModelValidator<RegularExpressionAttribute>
  • public class RequiredAttributeAdapter : DataAnnotationsModelValidator<RequiredAttribute>
  • public class StringLengthAttributeAdapter : DataAnnotationsModelValidator<StringLengthAttribute>

Все перечисленные классы являются адаптерами для передачи правил проверки данных на сторону клиента. Для этого они переопределяют метод GetClientValidationRules().

Провайдеры логики

Разумеется экземпляры наследников ModelValidator не создаются сами по себе. В ASP.NET MVC данная задача возложена на специальные классы, называемые провайдерами правил (логики) проверки. Давайте посмотрим на них внимательно.

ModelValidatorProvider

ModelValidatorProvider является базовым классом для всех провайдеров правил проверки. Он включает в себя один абстрактный метод:

public abstract class ModelValidatorProvider
{
    protected ModelValidatorProvider();

    public abstract IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context);
}

Метод GetValidators(), получив текущий контекст Контроллера и метаданные объекта, должен вернуть коллекцию правил для проверки значения Модели.

AssociatedValidatorProvider

AssociatedValidatorProvider это абстрактный класс, являющийся наследником ModelValidatorProvider. Он упрощает процесс создания провайдеров правил проверки, использующих атрибуты для своей работы. Для этого он реализует абстрактный метод базового класса, но объявляет свой его вариант. Таким образом в GetValidators() передается список атрибутов объекта.

public abstract class AssociatedValidatorProvider : ModelValidatorProvider
{
    protected AssociatedValidatorProvider();

    protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type);

    public override sealed IEnumerable<ModelValidator> GetValidators(
       ModelMetadata metadata, ControllerContext context);

    protected abstract IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);
}

Рассматриваемые далее четыре класса являются потомками от приведенных выше базовых классов. Они используются ASP.NET MVC 3 для создания правил проверки.

DataErrorInfoModelValidatorProvider

Класс DataErrorInfoModelValidatorProvider используется для Моделей, реализующих интерфейс IDataErrorInfo для проверки своих данных.

public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider
{
    public DataErrorInfoModelValidatorProvider();

    public override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context);
}

Сам интерфейс определен в пространстве имен System.ComponentModel.DataAnnotations:

public interface IDataErrorInfo
{
    string Error { get; }

    string this[string columnName] { get; }
}

В качестве результата свойства должны вернуть текст сообщения об ошибке, при ее наличии, или null при отсутствии замечаний. Проверка данных при этом производится на сервере. В начале опрашивается индексатор this[], которому в качестве индекса передаются имена свойств модели. И только в случае отсутствия ошибок, происходит обращение к свойству Error, которое может выдать обобщенное сообщение.

ClientDataTypeModelValidatorProvider

Класс ClientDataTypeModelValidatorProvider обеспечивает проверку на стороне клиента соответствия вводимого значения типу свойства Модели.

public class ClientDataTypeModelValidatorProvider : ModelValidatorProvider
{
    public ClientDataTypeModelValidatorProvider();

    public override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context);
}

Например, именно его правила сработают при попытке ввести буквы в поле, связанное с int в Модели. Данный провайдер влияет только на проверку на стороне клиента. Если он не будет задействован в веб-приложении, то это не отключит аналогичную проверку на сервере.

EmptyModelValidatorProvider

Класс EmptyModelValidatorProvider предназначен для Модели, которая не требует проверки данных. Его реализация возвращает пустой список правил:

public class EmptyModelValidatorProvider : ModelValidatorProvider
{
    public EmptyModelValidatorProvider() { }

    public override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context)
    {
         return Enumerable.Empty<ModelValidator>();
    }
}

DataAnnotationsModelValidatorProvider

Данный класс используется ядром ASP.NET MVC 3 для поддержки атрибутов DataAnnotations. Давайте посмотрим на его методы:

public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
{
    public DataAnnotationsModelValidatorProvider();

    public static bool AddImplicitRequiredAttributeForValueTypes { get; set; }

    protected override IEnumerable<ModelValidator> GetValidators(
        ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);

    public static void RegisterAdapter(Type attributeType, Type adapterType);

    public static void RegisterAdapterFactory(
        Type attributeType, DataAnnotationsModelValidationFactory factory);

    public static void RegisterDefaultAdapter(Type adapterType);

    public static void RegisterDefaultAdapterFactory(
        DataAnnotationsModelValidationFactory factory);

    public static void RegisterDefaultValidatableObjectAdapter(Type adapterType);

    public static void RegisterDefaultValidatableObjectAdapterFactory(
        DataAnnotationsValidatableObjectAdapterFactory factory);

    public static void RegisterValidatableObjectAdapter(Type modelType, Type adapterType);

    public static void RegisterValidatableObjectAdapterFactory(
        Type modelType, DataAnnotationsValidatableObjectAdapterFactory factory);
}

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

Необходимо отметить, что все атрибуты DataAnnotations являются наследниками ValidationAttribute. Это обязывает их реализовывать логику проверки вводимых значений. Кроме того, многие из них реализуют IClientValidatable для поддержки проверки данных на стороне клиента. Таким образом они содержат всю необходимую информацию и логику для реализации полной процедуры проверки. Методу GetValidators() остаётся только передать её в экземпляры ModelValidator и ModelClientValidationRule.

Установка провайдера правил проверки Модели

В отличии от провайдеров метаданных, ASP.NET MVC начиная с 3 версии позволяет задать список используемых провайдеров правил проверки данных. Для этого используется статическое свойство, принадлежащее классу ModelValidatorProviders. В этот список ASP.NET MVC 3 по умолчанию добавляет следующих провайдеров:

  • DataErrorInfoModelValidatorProvider
  • ClientDataTypeModelValidatorProvider
  • DataAnnotationsModelValidatorProvider

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

Сам список доступен для просмотра и модификации, используя стандартные методы и способы работы с ним: Add(), Clear(), foreach и т. д.

Изменяем список провайдеров

В завершении части проведем небольшой эксперимент. В Application_Start() создаваемого веб-приложения добавим следующий код:

protected void Application_Start()
{
    .........

    ModelValidatorProviders.Providers.Clear();
    ModelValidatorProviders.Providers.Add(new [ProviderType]);
}

В выделенной строке вместо [ProviderType] будем указывать одного из стандартных провайдеров. Посмотрим как изменится поведение веб-приложения:

DataErrorInfoModelValidatorProvider

Такой вариант приведет к отключению проверки в данном веб-приложении, т.к. ни один класс Модели не реализует IDataErrorInfo. Для примера, можно добавить поддержку интерфейса в любой их них. Пусть все его индексатор this[] и свойство Error всегда возвращают текст "Test message". В этом случае это сообщение будет отображено возле всех полей формы после проверки данных, отправленных на сервер.

ClientDataTypeModelValidatorProvider

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

DataAnnotationsModelValidatorProvider

Особых изменений заметно не будет, т.к. в данном веб-проекте вся проверка в данный момент реализована на атрибутах. Исчезнет только указанная выше проверка значения на соответствие типу.

EmptyModelValidatorProvider 

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

Уберем добавленный для эксперимента код. В следующей части реализуем провайдера правил проверки для класса PaymentModel. Использовать для его настройки по прежнему будем XML файл.

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

Виктор 28.03.2011 23:30:42

Спасибо за ваш блог. Грамотное изложение материала.

Игорь 29.03.2011 22:51:29

Большое спасибо. Очень полезный матерья, и очень хорошее изложение.

Филипп 10.07.2011 5:33:37

отличный материал, не отлипаю от блога уже трое суток! Smile На всякий случай, если собираетесь где нибудь издавать, стилистическое замечание: Подраздел "Поддержка DataAnnotation", второе и третье предложения - начинаются со слова "поэтому", меня за такие мелочи редактор вешал

Филипп 10.07.2011 5:54:51

P.S. В конце предложение "В выделенной строке вместо [ProviderType] будем указывать одного из стандартных провайдеров. Посмотрим как изменится поведение веб-приложения:", лучше "указывать один из стандартных провайдеров". Но это в принципе Вам решать Smile

@ Филипп: Стараюсь следить за стилем, но проскакивает (особенно по поводу "поэтому"). Учту замечания в новой версии текста.

Pingbacks and trackbacks (3)+

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