Реализация поддержки правил и разработка провайдера конфигурации Модели завершена. Давайте теперь задействуем его в проекте и посмотрим на него в действии.
Итоговая версия провайдера конфигурации Модели
Прежде всего добавим созданные провайдеры в класс ModelConfigurationProvider.
Извне провайдер правил проверки данных будет доступен как экземпляр класса ModelValidatorProvider. Если использовать только это свойство, то будут видны только методы, принадлежащие этому типу. Поэтому создадим закрытое поле, в котором разместим экземпляр ModelValidatorProviderAdapter. Это позволит обращаться ко всем его методам и свойствам. В частности, они потребуются для пополнения списка поддерживаемых реализацией правил.
Для провайдера метаданных Модели отдельное поле не требуется. Его сразу можно объявить как свойство типа IModelMetadataProvider.
Кроме того, создадим метод AddValidatiorRule(), которой позволит пользователям добавлять новые реализации ModelValidatorRule в коллекцию Rules провайдера правил проверки данных. Его первым параметром будет имя элемента, с которым связано само правило. В качестве второго параметра будет передаваться ссылка на экземпляр реализации.
В итоге этих изменений, часть класса ModelConfigurationProvider, размещенная в файле ModelConfigurationProvider.cs, будет выглядеть следующим образом:
/// 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.Web.Mvc;
using System.Xml.Linq;
using MVCDemo.Models.Configuration.Rules;
using MVCDemo.Models.Configuration.Sources;
using MVCDemo.Models.MetadataProviders;
public partial class ModelConfigurationProvider
{
#region Private fields
private readonly Dictionary<Type, ModelConfiguraion> _cache;
private readonly ModelValidatorProviderAdapter _validatorProvider;
#endregion
public ModelConfigurationProvider()
{
this.Sources = new List<IModelConfigurationSource>();
this._cache = new Dictionary<Type, ModelConfiguraion>();
this.MetadataProvider = new ModelMetadataProviderAdapter(this);
this._validatorProvider = new ModelValidatorProviderAdapter(this);
}
#region Properties
public IModelMetadataProvider MetadataProvider { get; private set; }
public ModelValidatorProvider ValidatorProvider
{
get { return this._validatorProvider; }
}
public List<IModelConfigurationSource> Sources { get; private set; }
#endregion
public void AddValidatiorRule(string ruleName, IModelValidatorRule rule)
{
this._validatorProvider.Rules.Add(ruleName, rule);
}
private ModelConfiguraion GetModelConfiguraion(Type modelType)
{
foreach (var modelConfiguraion in this._cache) {
if (modelConfiguraion.Key.Equals(modelType)) {
return modelConfiguraion.Value;
}
}
foreach (var source in this.Sources) {
bool useCache = false;
XElement config = source.GetModelConfiguration(modelType, out useCache);
if (config != null) {
var data = new ModelConfiguraion(config);
if (useCache) {
this._cache.Add(modelType, data);
}
return data;
}
}
return null;
}
private class ModelConfiguraion
{
public ModelConfiguraion(XElement source)
{
this.Source = source;
var resourceAttr = this.Source.Attribute("resource");
if (resourceAttr != null) {
this.DefaultResourceType = Type.GetType(resourceAttr.Value, throwOnError: true);
}
}
public XElement Source { get; private set; }
public Type DefaultResourceType { get; private set; }
}
}
}
Перейдем практической части и подключим провайдер конфигурации Модели в проект.
Создаем новое описание Модели
Id и UserLogin
Начнем с описания класса Модели PaymentModel. Прежде всего, удалим все элементы <metadata>, устанавливающие свойство IsRequired в значение true. Причину этого действия разберем чуть позднее. А пока рассмотрим добавляемые правила для каждого свойства:
<?xml version="1.0" encoding="utf-8" ?>
<model resource="MVCDemo.Resources.Models.PaymentRes">
<property name="Id">
<metadata name="ShowForDisplay" value="false" />
<metadata name="ShowForEdit" value="false" />
</property>
<property name="UserLogin">
<metadata name="DisplayName" resname="UserLogin" />
<rule name="Require" resname="FieldIsRequired" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="BlockHtml" resname="HtmlTagsNotAllowed" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="StringLength" max="64" resname="StringTooLong" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="Remote" controller="Payments" action="IsLoginNotExists" method="POST"
resname="InvalidUserLogin" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="CustomValidation" type="MVCDemo.Models.Validators.PaymentModelValidator" method="IsUserExists"
resname="InvalidUserLogin" resource="MVCDemo.Resources.Shared.ErrorsRes" />
</property>
Свойство Id является системным не нужно для вывода на форму. Ему уже присвоены соответствующие метённые. Поэтому сразу перейдем к следующему свойству UserLogin. Оно получило сразу четыре правила. Рассмотрим их по очереди:
- Require – указывает на то, что данное поле необходимо заполнить. Как можно понять из исходного кода, его сообщение будет использовать текст под имением FieldIsRequired из общего файла ресурсов ErrorsRes.resx.
- BlockHtml – отключит в нем ввод элементов HTML.
- StringLength – обеспечит ограничит длину строки до 64 символа. Минимальное значение контролировать нет необходимости, т.к. есть четверное правило...
- Remote – организует удаленную проверку значения, которая требует обязательного совпадения введенного имени с уже существующим. Требует указать Контроллер и Действие, которые будут использоваться для обслуживания этой проверки.
- CustomValidation – данное правило гарантирует, что имя пользователя существует в базе данных. Метод для него создадим чуть позже.
Для правила Remote в ресурсах веб-приложения зададим текст сообщения об ошибке:
Файл Resources\Shared\ErrorsRes.resx |
InvalidUserLogin |
Invalid user login. |
Неизвестное имя пользователя |
Для этого же правила реализуем также Действие IsLoginNotExists в Контролере PaymentsController:
namespace MVCDemo.Controllers
{
using System;
using System.Web.Mvc;
using MVCDemo.Models;
public class PaymentsController : Controller
{
.........
// POST: /Payments/IsLoginNotExists/
[HttpPost]
public JsonResult IsLoginNotExists(string userLogin)
{
var userRepository = new UserRepository();
return this.Json(userRepository.IsLoginExists(userLogin));
}
}
}
Обратите внимание, что имя параметра метода IsLoginNotExists() совпадает, без учета регистра букв, с именем свойства, от которого необходимо получить данные. Если это правило не выполнить, то полученная от формы информация не будет присвоена в данную переменную.
Осталось создать метод для аналогичной проверки на стороне сервера, которая указана в правиле CustomValidation. Для этого реализуем класс PaymentModelValidator, который создадим в папке Models\Validators:
namespace MVCDemo.Models.Validators
{
using System.ComponentModel.DataAnnotations;
using MVCDemo.Resources.Shared;
public static class PaymentModelValidator
{
public static ValidationResult IsUserExists(
string userLogin, ValidationContext context)
{
var userRepository = new UserRepository();
return userRepository.IsLoginExists(userLogin) ?
ValidationResult.Success :
new ValidationResult(ErrorsRes.InvalidUserLogin);
}
}
}
Date
<property name="Date">
<metadata name="DisplayName" resname="Date" />
<metadata name="DataTypeName" value="DateTime" />
<rule name="Require" resname="FieldIsRequired" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="RegExp" resname="IncorrectDateTimeFormat"
regexp="(\d{1,2})[-/.](\d{1,2})[-/.](?:\d{4}|\d{2})[ ](\d{1,2}):(\d{1,2}):{0,1}(\d{0,2})" />
</property>
Будем контролировать обязательность ввода поля Date, а так же формат вводимого значения. Установим его в виде "DD.MM.YYYY hh:mm:ss" с помощью регулярного выражения. Кроме того, потребуется создать сообщение об ошибке:
Файл Resources\Models\PaymentRes.resx |
IncorrectDateTimeFormat |
Date format should be DD.MM.YYYY hh:mm:ss |
ERROR: Неверный формат при указании даты и времени |
Amount
<property name="Amount">
<metadata name="DisplayName" resname="Amount" />
<metadata name="DataTypeName" value="Currency" />
<rule name="Require" resname="FieldIsRequired" resource="MVCDemo.Resources.Shared.ErrorsRes" />
<rule name="Range" min="0.01" max="100000000.0" type="System.Double" resname="IncorrectAmountValue" />
</property>
Обратите внимание на используемый разделитель в записи чисел. Он должен совпадать с значением, установленным в региональных настройках текущего компьютера. Это необходимо для корректного преобразования заданных строкой значений к типу "System.Double".
Свойство Amout является числом и по определению не может быть null. Для таких полей ввод их значения требуется автоматически. Может возникнуть вопрос зачем добавлено правило Require? Однако цель здесь очень простая – установка сообщения об ошибке для данного поля. Особенно это может быть необходимо, если сами сообщения локализованы.
Кроме того, будем контролировать с помощью правила Range соответствие вводимого значения заданному диапазону. При этом используется тот же тип System.Double, что и у свойства Amount. Поскольку это сумма в рублях, то минимум установим равным одной копейке. Нет смысла заводить платеж на ноль рублей роль копеек. Максимум приравняем к ста миллионам рублей.
Кроме того, добавим новое сообщение об ошибке для данного правила:
Файл Resources\Models\PaymentRes.resx |
IncorrectAmountValue |
Incorrect amount value. |
ERROR: Отрицательное или очень большое значение суммы |
Info
Для последнего свойства Info просто ограничим ввод HTML элементов:
<property name="Info">
<metadata name="DisplayName" resname="Info" />
<metadata name="DataTypeName" value="MultilineText" />
<rule name="BlockHtml" resname="HtmlTagsNotAllowed" resource="MVCDemo.Resources.Shared.ErrorsRes" />
</property>
</model>
Подключение провайдера конфигурации Модели
После того, как описание Модели задано, все что необходимо, это добавить несколько строк в файл Global.asax.cs. Посмотрим в начале на исходный код, а затем разберем его в подробностях:
namespace MVCDemo
{
using System.Web.Mvc;
using System.Web.Routing;
using MVCDemo.Models.Configuration;
using MVCDemo.Models.Configuration.Rules;
using MVCDemo.Models.Configuration.Sources;
using MVCDemo.Models.MetadataProviders;
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
.........
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var modelProvider = new ModelConfigurationProvider();
modelProvider.Sources.Add(
new XmlModelConfigurationSource(
Server.MapPath("~/App_Data/ModelMetadata/")));
var manager = new ModelMetadataProvidersManager(ModelMetadataProviders.Current);
manager.Providers.Add(modelProvider.MetadataProvider);
ModelMetadataProviders.Current = manager;
modelProvider.AddValidatiorRule("BlockHtml", new BlockHtmlRule());
modelProvider.AddValidatiorRule("Equal", new EqualRule());
modelProvider.AddValidatiorRule("StringLengthRange", new StringLengthRangeRule());
ModelValidatorProviders.Providers.Insert(0, modelProvider.ValidatorProvider);
}
}
}
В первую очередь отметим подключение соответствующих пространств имен.
Затем перейдем к методу Application_Start(). В нем создадим экземпляр ModelConfigurationProvider и установим его как провайдера метаданных и правил проверки данных для текущей Модели. Кроме того, подключим реализации пользовательских трех правил: BlockHtml, Equal и StringLengthRange.
В завершении стоит обратить внимание на способ добавления провайдера правил проверки в коллекцию ModelValidatorProviders.Providers. Как правило, можно встретить примеры, когда используется метод Add(). Т.е. новый провайдер будет последним в их списке. В данном случае используется вставка экземпляра в начало списка.
Это необходимо для того, чтобы экземпляры ModelValidator были созданы реализованным провайдером о того, как начнет свою работу DataAnnotationsModelValidatorProvider. В противном случае, даже без явного указания он автоматически создаст для некоторых свойств атрибут [Required] и соответствующее правило проверки. Если после этого добавить свою копию атрибута, то это приведет к ош��бке на этапе выполнения. Ведь будут два правила с одинаковыми "уникальными" именами.
Отказаться от создания [Required] нет смысла, т.к. зачастую необходимо задать текст сообщения об ошибке на родном для пользователя языке.
Чтобы избежать этого, необходимо создать свою версию [Required] заранее. Поэтому воспользуемся вставкой своего провайдера правил проверки в начало коллекции.
Реализация провайдера конфигурации Модели завершена. Можно запустить проект и убедиться в его работоспособности. На страницы добавления данных платежа теперь есть контроль вводимых значений.
Исходный код проекта (C#, Visual Studio 2010):
MVCDemo-Part22.zip (516 Kb)