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

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

Создаем поддержку стандартных атрибутов

В прошлой части был создан абстрактный базовый класс ModelValidatorRule. Его и будем использовать в качестве основы для разработки. Чтобы понять, какой предстоит объём работы, перечислим атрибуты:

  • Стандартные:
    • [Range]
    • [RegularExpression]
    • [Remote]
    • [Require]
    • [StringLength]
    • [CustomValidation]
  • Созданные для данного проекта:
    • [BlockHtml]
    • [Equal]
    • [StringLengthRange]

Начнем разработку со стандартных правил.

Поддержка атрибута [Range] – RangeRule

Атрибут [Range] определяет диапазон, в который должно входить значение параметра. Поэтому будем требовать от элемента <rule> два новых атрибута: min и max. Кроме того, необходимо будет указать тип самих значений. По умолчанию это будет System.Int32.

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

/// 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.IO;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public class RangeRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            string minValue = xmlElement.GetValueOrNull("min");
            if (minValue == null) {
                throw new InvalidDataException(
                    string.Format("Min value can't be null. Element: {0}.", xmlElement));
            }

            string maxValue = xmlElement.GetValueOrNull("max");
            if (maxValue == null) {
                throw new InvalidDataException(
                    string.Format("Max value can't be null. Element: {0}.", xmlElement));
            }

            string typeName = xmlElement.GetValueOrNull("type");
            if (typeName == null) {
                typeName = "System.Int32";
            }

            Type rangeType = Type.GetType(typeName);
            if (rangeType == null) {
                throw new InvalidDataException(
                    string.Format("Unknown type: {0}. Element: {1}.", typeName, xmlElement));
            }

            var attribute = new RangeAttribute(rangeType, minValue, maxValue);
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);

            return new RangeAttributeAdapter(metadata, context, attribute);
        }
    }
}

Обратите внимание на вызов метода BindErrorMessageToAttribte() после создания экземпляра самого атрибута проверки данных. Он устанавливает текст сообщения об ошибке, используя стандартные атрибуты элемента <rule>.

Результатом работы метода Create() является экземпляр RangeAttributeAdapter, который представляет реализацию ModelValidator для атрибута RuleAttribute.

Поддержка атрибута [RegularExpression] – RegularExpressionRule

Для атрибута [RegularExpression] также потребуется дополнительное значение regexp, определяющее используемое регулярное выражение. Его отсутствие будет являться исключительной ситуацией.

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

/// 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.IO;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public class RegularExpressionRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            string regExp = xmlElement.GetValueOrNull("regexp");
            if (regExp == null) {
                throw new InvalidDataException(
                    string.Format("The regular expression was not set. Element: {0}", xmlElement));
            }

            var attribute = new RegularExpressionAttribute(regExp);
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);

            return new RegularExpressionAttributeAdapter(metadata, context, attribute);
        }
    }
}

Возвращаемый методом Create() экземпляр RegularExpressionAttributeAdapter является реализацией ModelValidator для атрибута RegularExpressionAttribute. В остальном реализация похожа на предыдущую и не требует дополнительных комментариев.

Поддержка атрибута [Remote] – RemoteRule

Атрибут [Remote] осуществляет удаленную проверку значения. Выберем вариант его инициализации с указанием Контроллера и Действия. Соответственно, потребуются их значения controller и action. Кроме того, предоставим контроль над используемым методом запроса к сервер через атрибут method. Он не является обязательным и может принимать значения POST или GET.

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

/// 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.IO;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public class RemoteRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            string controller = xmlElement.GetValueOrNull("controller");
            if (controller == null) {
                throw new InvalidDataException(
                    string.Format("The controller was not set. Element: {0}", xmlElement));
            }

            string action = xmlElement.GetValueOrNull("action");
            if (action == null) {
                throw new InvalidDataException(
                    string.Format("The action was not set. Element: {0}", xmlElement));
            }
            string httpMethod = xmlElement.GetValueOrNull("method"); 

            var attribute = new RemoteAttribute(action, controller);
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);
            if (httpMethod != null) {
                attribute.HttpMethod = httpMethod;
            }

            return new DataAnnotationsModelValidator<RemoteAttribute>(metadata, context, attribute);
        }
    }
}

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

Поддержка атрибута [Require] – RequireRule

Атрибут [Require] указывает на необходимость ввода значения в связанное с ним свойство Модели. Может возникнуть вопрос: но ведь в её метаданных уже есть свойство IsRequired? Разумеется есть, но оно не позволяет определить свое сообщение об ошибке, а это зачастую очень необходимо в приложениях.

Дополнительных параметров [Require] не требует, поэтому код реализации будет очень коротким. Создадим для него в папке Models\Configuration\Rules класс RequireRule:

/// 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;

    public class RequireRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            metadata.IsRequired = false;

            var attribute = new RequiredAttribute();
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);

            return new RequiredAttributeAdapter(metadata, context, attribute);
        }
    }
}

Несмотря на кажущуюся простоту реализации обратите внимание на одним важный момент: свойство IsRequired метаданных Модели принудительно устанавливается в значение false. Если этого не сделать, то другие провайдеры правил могут создать правило проверки на обязательность ввода поля. А это приведет к конфликту одинаковых имен в JavaScript. Кстати, немного забегая вперед можно сказать что провайдеры правил DataAnnotationsModelValidatorProvider обязательно так и сделает.

Поддержка атрибута [StringLength] – StringLengthRule

Атрибут [StringLength] ограничивает максимальную длину строки. Поэтому для него будем требовать указания значения max.

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

/// 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.IO;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public class StringLengthRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            string maxLenght = xmlElement.GetValueOrNull("max");
            if (maxLenght == null) {
                throw new InvalidDataException(
                    string.Format(
                        "The maximum string length was not set. Element: {0}", xmlElement));
            }

            var attribute = new StringLengthAttribute(Convert.ToInt32(maxLenght));
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);

            return new StringLengthAttributeAdapter(metadata, context, attribute);
        }
    }
}

Поддержка атрибута [CustomValidation] – CustomValidationRule

Атрибут [CustomValidation] позволяет указать метод, который будет использовать для проверки значения свойства Модели. В качестве параметров необходимо будет указать

  • type – тип класса, в котором содержится метод;
  • method – имя этого метода.

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

/// 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.IO;
    using System.Web.Mvc;
    using System.Xml.Linq;
    using MVCDemo.Models.Extensions;

    public class CustomValidationRule : ModelValidatorRule
    {
        public override ModelValidator Create(
            XElement xmlElement,
            Type defaultResourceType,
            ModelMetadata metadata,
            ControllerContext context)
        {
            string classTypeName = xmlElement.GetValueOrNull("type");
            if (classTypeName == null) {
                throw new InvalidDataException(
                    string.Format("The class type was not set. Element: {0}", xmlElement));
            }

            Type classType = Type.GetType(classTypeName, throwOnError: true);
            if (classType == null) {
                throw new InvalidDataException(
                    string.Format("Unknown class type. Element: {0}", xmlElement));
            }

            string method = xmlElement.GetValueOrNull("method");
            if (method == null) {
                throw new InvalidDataException(
                    string.Format("The action was not set. Element: {0}", xmlElement));
            }

            var attribute = new CustomValidationAttribute(classType, method);
            this.BindErrorMessageToAttribte(attribute, xmlElement, defaultResourceType);

            return new DataAnnotationsModelValidator<CustomValidationAttribute>(
                metadata, context, attribute);
        }
    }
}

Подключаем поддержку атрибутов в провайдер

Будет вполне логично, если поддержка стандартных атрибут будет сразу активирована в провайдере правил. Для этого в его конструкторе добавим экземпляры приведённых выше классов в список правил:

/// 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 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>();

                // add default rules
                this.Rules.Add("Require", new RequireRule());
                this.Rules.Add("Range", new RangeRule());
                this.Rules.Add("RegExp", new RegularExpressionRule());
                this.Rules.Add("StringLength", new StringLengthRule());
                this.Rules.Add("Remote", new RemoteRule());
            }

            ..........
        }
    }
}

В следующей части завершим разработку, создав поддержку для пользовательских атрибутов. >>

Pingbacks and trackbacks (2)+

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