Разработаем реализации интерфейса 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());
}
..........
}
}
}
В следующей части завершим разработку, создав поддержку для пользовательских атрибутов. >>