Andrey on .NET | Часть 22.3 – Провайдер конфигурации Модели. Провайдер правил

Часть 22.3 – Провайдер конфигурации Модели. Провайдер правил

В данной части разберемся как создать провайдера правил и какие еще классы нам понадобятся для завершения задуманной реализации провайдера конфигураций.

Разработка провайдера правил проверки данных Модели

В папке Models\Configuration создадим класс ModelValidatorProviderAdapter для реализации провайдера правил проверки данных Модели. Он будет также являться закрытым и вложенным в другой класс – ModelConfigurationProvider.

В качестве базового класса необходимо использовать один из абстрактных классов провайдеров правил проверки: ModelValidatorProvider или AssociatedValidatorProvider. Выберем первый, поскольку данная реализация будет полагаться только на данные полученные от провайдера конфигурации Модели. Обратите внимание, это не отменяет использование атрибутов, т.к. ASP.NET MVC 3 позволяет подключить и использовать одновременно несколько провайдеров правил в веб-приложении. При этом все они будут вызваны для создания правил проверки данных текущей Модели.

Язык описания правил

Для указания правил расширим язык описания информации об объекте новым элементом <rule>. Он также будет указываться для конкретного свойства и иметь четыре стандартных атрибута:

  • name – (обязательный) имя правила;
  • errormessage – предоставляет текст сообщения об ошибке;
  • resname – определяет имя, для получения сообщения из файла ресурсов;
  • resource – позволяет переопределить тип ресурса для данного элемента;

Как можно догадаться, необходимо обязательно указать или errormessage или resname.

Элемент <rule> по сути описывает .NET-атрибут проверки данных. В общем случае невозможно заранее предсказать какие параметры он потребует. Следовательно, все остальные атрибуты элемента конфигурации будут определяться правилом, которое он представляет.

Интерфейс для правил

Перед тем как начать разработку, необходимо решить один важный момент. Метаданные для определённого свойства Модели всегда представлены одним экземпляром ModelMetadata. В отличии от этого, каждое правило в ASP.NET MVC 3 представлено собственным классом, который связан с конкретными атрибутами. Таким образом, учитывая что атрибуты могут иметь любой набор параметров конструктора, возникает вопрос о схеме их порождения.

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

Для хранения их реализаций создадим в папке Configuration папку Rules. В неё и поместим интерфейс IModelValidatorRule, описывающий взаимодействие провайдера правил с их конверторами.

///Copyright (c) 2011 Andrey Veselov. All rights reserved.
/// WebSite: http://andrey.moveax.ru 
/// Email: andrey@moveax.ru
/// This source is subject to the Microsoft Public License (Ms-PL).

namespace MVCDemo.Models.Configuration.Rules
{
    using System;
    using System.Web.Mvc;
    using System.Xml.Linq;

    public interface IModelValidatorRule
    {
        ModelValidator Create(
            XElement xmlElement, 
            Type defaultResourceType, 
            ModelMetadata metadata, 
            ControllerContext context);
    }
}

Назначение его единственного метода вполне очевидно: создать экземпляр класса, реализующий ModelValidator, для указанного элемента конфигурации xmlElement. Сохраним файл IModelValidatorRule.cs. К непосредственной реализации правил вернемся чуть позже. А сейчас приступим к непосредственному созданию класса ModelConfigurationProvider.

Как и в раньше, начнем с создания полей, свойств и конструкторов.

/// Copyright (c) 2011 Andrey Veselov. All rights reserved.
/// WebSite: http://andrey.moveax.ru 
/// Email: andrey@moveax.ru
/// This source is subject to the Microsoft Public License (Ms-PL).

namespace MVCDemo.Models.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web.Mvc;
    using MVCDemo.Models.Configuration.Rules;
    using MVCDemo.Models.Extensions;

    public partial class ModelConfigurationProvider
    {
        private class ModelValidatorProviderAdapter : ModelValidatorProvider
        {
            private readonly ModelConfigurationProvider _cfgProvider;

            public ModelValidatorProviderAdapter(ModelConfigurationProvider cfgProvider)
            {
                if (cfgProvider == null) {
                    throw new ArgumentNullException();
                }

                this._cfgProvider = cfgProvider;

                this.Rules = new Dictionary<string, IModelValidatorRule>();
            }

            public Dictionary<string, IModelValidatorRule> Rules { get; private set; }

Закрытое поле _cfgProvider предназначено для хранения ссылки на провайдера конфигурации. В дальнейшем оно будет использоваться для получения информации об указанной Модели.

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

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

Прежде всего GetValidators() это переопределение одноименного абстрактного метода базового класса ModelValidatorProvider. Его задача достаточно простая. Получив от конфигуратора Модели список свойств, он для каждого их них перебирает все вложенные элементы <rule>. В коллекции Rules по имени каждого правила осуществляется подбор соответствующей реализации IModelValidatorRule. В случае успеха происходит обращение к ней за экземпляром ModelValidator.

#region ModelValidatorProvider method override

            public override IEnumerable<ModelValidator> GetValidators(
                ModelMetadata metadata, ControllerContext context)
            {
                var cfgData = this._cfgProvider.GetModelConfiguraion(metadata.ContainerType);
                if (cfgData == null) {
                    yield break;
                }

                var xmlPropertyElement = cfgData.Source.Elements("property").FirstOrDefault(
                    element => string.Equals(element.Attribute("name").Value, metadata.PropertyName));

                if (xmlPropertyElement == null) {
                    yield break;
                }

                // Get rules for the property using XML attributes.
                foreach (var element in xmlPropertyElement.Elements()) {
                    if (!string.Equals(element.Name.LocalName, "rule")) {
                        continue;
                    }

                    string ruleName = element.GetValueOrNull("name");
                    if (ruleName == null) {
                        throw new InvalidDataException(
                            string.Format("Incorrect rule '{0}'.", element));
                    }

                    IModelValidatorRule factoryMethod = this.Rules[ruleName];
                    yield return factoryMethod.Create(
                        element, cfgData.DefaultResourceType, metadata, context);
                }
            }

            #endregion
        }
    }
}

Обратите внимание, что при указании в конфигурации имени несуществующего правила будет выброшено исключение при обращении к this.Rules[ruleName]. Если необходимо игнорировать ошибки, то можно вместо этого воспользоваться методом this.Rules.FindFirstOrDefault(). В этом случае потребуется дополнительное сравнение factoryMethod с пустой ссылкой (null), чтобы определить найдено ли правило.

Реализация IModelValidatorRule

В этой части "правилами" будут именоваться именно реализации указанного в заголовке интерфейса. И как уже было отмечено, все они имеют четыре стандартных атрибута. Создавать в каждой из них метод их получения было бы не правильно. Поэтому начнем с абстрактного базового класса.

Абстрактный базовый класс ModelValidatorRule

На данный класс возложим ответственность за получение значений трех стандартных атрибутов элемента <rule>. Также, поскольку все используемые атрибуты являются наследниками ValidationRule, то здесь же назначим им прочитанные значения.

В папке Models\Configuration\Rules создадим класс ModelValidatorRule:

/// Copyright (c) 2011 Andrey Veselov. All rights reserved.
/// WebSite: http://andrey.moveax.ru 
/// Email: andrey@moveax.ru
/// This source is subject to the Microsoft Public License (Ms-PL).

namespace MVCDemo.Models.Configuration.Rules
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public abstract class ModelValidatorRule : IModelValidatorRule
    {
        public abstract ModelValidator Create(
            XElement xmlElement, 
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context);

        protected void BindErrorMessageToAttribte(
            ValidationAttribute attribute, XElement xmlElement, Type defaultResourceType)
        {
            string errorMessage = xmlElement.GetValueOrNull("errormessage");
            if (errorMessage != null) {
                attribute.ErrorMessage = errorMessage;
                return;
            }

            string errorMessageResName = xmlElement.GetValueOrNull("resname");
            if (errorMessageResName != null) {
                attribute.ErrorMessageResourceName = errorMessageResName;

                string errorMessageResType = xmlElement.GetValueOrNull("resource");
                attribute.ErrorMessageResourceType =
                    (errorMessageResType != null) ?
                    Type.GetType(errorMessageResType, throwOnError: true) : defaultResourceType;
            }
        }
    }
}

Поскольку нельзя оставить без внимания интерфейс IModelValidatorRule, то определим для него абстрактный метод. Таким образом, все наследники данного класса должны будут его реализовать.

Метод BindErrorMessageToAttribte() предназначен для получения стандартных значений из переданного ему элемента. При этом, если задано текстовое сообщение (errormessage), то указание на ресурс веб-приложения (resname) игнорируется. Также осуществляется присвоение полученных значений переданному экземпляру атрибута проверки данных attribute.

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

В следующей части рассмотрим реализации ModelValidatorRule для существующих правил. >>

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

Found you web site as a result of digg I must say I m impressed with your blogposts!

Pingbacks and trackbacks (3)+

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