ASP.NET
Проверка модели и метаданные в ASP.NET MVC 2
Скотт Аллен
Одна из новинок, добавленных в ASP.NET MVC 2, — возможность проверки пользовательского ввода как на сервере, так и на клиенте. От вас требуется лишь предоставить инфраструктуре некоторую информацию о данных, которые вам нужно проверить, и всю черную работу она возьмет на себя.
Эта функция крайне полезна тем, кто пишет в ASP.NET MVC 1.0 собственный код проверки и собственные редакторы связей модели (model binders) для выполнения простой проверки модели. В этой статье я рассмотрю встроенную поддержку проверки в ASP.NET MVC 2.
Но, прежде чем обсуждать новые возможности, я хочу ненадолго вернуться к старой методологии. Средства проверки в ASP.NET Web Forms верно служили мне многие годы. Думаю, будет полезно дать краткий обзор по ним, чтобы понять, что должна предоставлять идеальная инфраструктура.
Управление проверкой
Если вы когда-нибудь пользовались ASP.NET Web Forms, то знаете, что в веб-форму сравнительно нетрудно добавить логику проверки. Правила проверки выражались с применением элементов управления. Например, если вы хотели быть уверенным в том, что пользователь действительно вводит текст в элемент управления TextBox, то просто добавляли элемент управления RequiredFieldValidator, указывающий на TextBox:
<form id="form1" runat="server">
<asp:TextBox runat="server" ID="_userName" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="_userName"
ErrorMessage="Please enter a username" />
<asp:Button runat="server" ID="_submit" Text="Submit" />
</form>
RequiredFieldValidator инкапсулирует клиентскую и серверную логику проверки в данном случае того, что введено имя пользователя. Чтобы обеспечить проверку на клиентской стороне, этот элемент генерирует JavaScript-код в клиентском браузере, и этот сценарий гарантирует, что пользовательский ввод отвечает всем правилам проверки до обратной передачи формы на сервер.
Задумайтесь, что предлагают эти «проверяющие» элементы управления Web Forms:
- вы можете декларативно выражать правила проверки для страницы в одном месте;
- проверка на клиентской стороне предотвращает обмен данными с сервером, если пользовательский ввод не прошел правила проверки;
- проверка на серверной стороне делает бесполезной злонамеренное вмешательство в клиентский сценарий;
- логика на серверной и клиентской сторонах остается в синхронизированном состоянии, не вызывая проблем в сопровождении.
Но в ASP.NET MVC нельзя использовать эти элементы управления и остаться верным духу проектировочного шаблона MVC. К счастью, во второй версии этой инфраструктуры есть кое-что получше.
Элементы управления и модели
Элемент управления Web Forms вроде TextBox можно рассматривать как простой контейнер пользовательских данных. Вы можете заполнить его начальным значением и показать пользователю, а также можете извлечь любое введенное или измененное пользователем значение, обратившись к элементу управления после обратной передачи (postback). При использовании проектировочного шаблона MVC «M» (Model) играет ту же роль, что и контейнер данных. Вы заполняете модель информацией, которую нужно передать пользователю, а она возвращает в приложение модифицированные значения. Таким образом, модель — идеальное место для выражения правил проверки и ограничений.
Вот готовый пример. Если вы создаете приложение ASP.NET MVC 2, в его проекте одним из контроллеров является AccountController. Он отвечает за обработку запросов на регистрацию новых пользователей, а также запросов на вход и смену паролей. Каждая из этих операций использует выделенный объект модели. Вы можете найти эти модели в файле AccountModels.cs в папке Models. Скажем, без правил проверки класс RegisterModel выглядит так:
public class RegisterModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
Операция Register контроллера AccountController принимает экземпляр этого класса RegisterModel в качестве параметра:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
// ...
}
Если модель правильна, операция Register пересылает ее информацию сервису, создающему нового пользователя.
RegisterModel — отличный пример модели, специфичной для представления, или модели представления (view model). Это модель, которая предназначена работать не с конкретной таблицей базы данных, методом веб-сервиса или бизнес-объектом, а с определенным представлением (Register.aspx, часть которого показана на рис. 1). Каждое свойство модели сопоставлено с элементом ввода в представлении. Советую использовать вам модели представлений, так как они упрощают многие ситуации в MVC-разработке, в том числе проверку.
Рис. Регистрационная информация
Модели и метаданные
Когда пользователь вводит информацию, относящуюся к учетной записи, в представление Register, инфраструктура MVC гарантирует, что пользователь предоставит свои UserName и Email. Инфраструктура также гарантирует совпадение строк Password и ConfirmPassword и то, что пароль будет иметь длину не менее шести символов. Как это все делается? Анализом и манипуляциями над метаданными, присоединенными к классу RegisterModel. На рис. 2 показан класс RegisterModel со своими атрибутами проверки.
Рис. Класс RegisterModel с атрибутами проверки
[PropertiesMustMatch("Password", "ConfirmPassword",
ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Email { get; set; }
[Required]
[ValidatePasswordLength]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
}
Когда пользователь передает представление Register, редактор связей модели по умолчанию в ASP.NET MVC пытается сконструировать новый экземпляр класса RegisterModel для передачи в качестве параметра операции Register в AccountController. Редактор связей модели извлекает информацию из текущего запроса и заполняет ею объект RegisterModel. Например, он может автоматически найти POST-значение HTML-элемента ввода с именем UserName и записать это значение в свойство UserName, принадлежащее RegisterModel. Такое поведение присутствовало в ASP.NET MVC со времен версии 1.0, так что оно не станет для вас неожиданным, если вы уже пользовались этой инфраструктурой.
А вот что нового в версии 2, так это то, как редактор связей модели по умолчанию будет запрашивать еще и провайдер метаданных на наличие любых метаданных, доступных для объекта RegisterModel. Этот процесс в конечном счете порождает производный объект ModelMetaData, который не только описывает правила проверки, сопоставленные с моделью, но и содержит информацию, определяющую отображение модели в представлении. Член группы ASP.NET Брэд Уилсон (Brad Wilson) опубликовал в своем блоге серию глубоких статей о том, как эти метаданные модели могут влиять на отображение модели через шаблоны. Первую статью серии см. по ссылке bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html.
Как только редактор связей модели получает объект ModelMetaData, сопоставленный с моделью, он может использовать содержащиеся в этом объекте проверочные метаданные (validation metadata) для проверки объекта модели. По умолчанию ASP.NET MVC использует метаданные из атрибутов аннотаций данных (data annotation attributes) наподобие [Required]. Конечно, ASP.NET MVC является расширяемой, поэтому, если вам нужен другой источник метаданных модели, вы можете реализовать собственный провайдер метаданных. Бен Шейрман (Ben Scheirman) опубликовал кое-какую интересную информацию на эту тему в статье «Customizing ASP.NET MVC 2 — Metadata and Validation» по ссылке dotnetslackers.com/articles/aspnet/customizing-asp-net-mvc-2-metadata-and-validation.aspx.
Аннотации данных
В качестве небольшого отступления замечу, что вы можете создавать свои атрибуты проверки (я покажу это позднее), но [Required] является одним из стандартных атрибутов проверки, которые содержатся в сборке System.ComponentModel.DataAnnotations. На рис. 3 показан полный список атрибутов проверки из этой сборки.
Рис. 3 Список атрибутов проверки из сборки
Атрибут | Описание |
StringLength | Указывает максимальную длину строку, разрешенную в поле данных |
Required | Указывает, что значение в поле данных обязательно |
RegularExpression | Указывает, что значение в поле данных должно совпадать с результатом заданного регулярного выражения |
Range | Указывает допустимый диапазон числовых значений в поле данных |
DataType | Указывает имя дополнительного типа, сопоставляемого с полем данных (одним из перечислимых значений DataType вроде EmailAddress, Url или Password) |
Эти атрибуты быстро становятся частью Microsoft .NET Framework. Вы можете использовать эти атрибуты не только в приложении ASP.NET MVC, но и в ASP.NET Dynamic Data, Silverlight и RIA-сервисах Silverlight.
Просмотр результатов проверки
Если вы подготовили метаданные, ошибки будут автоматически отображаться в представлении, как только пользователь введет неправильные данные. На рис. 3 показано, как выглядит представление Register, когда пользователь щелкнул кнопку Register, не предоставив никакой информации.
Рис. Проверка закончилась неудачей
Экран на рис. 3 был сформирован с применением некоторых новых вспомогательных HTML-функций в ASP.NET MVC 2, в том числе ValidationMessageFor. Последняя управляет размещением сообщения о неудачной проверке конкретного поля данных. Фрагмент из Register.aspx, демонстрирующий, как использовать вспомогательные функции ValidationMessageFor и ValidationSummary, показан на рис. 4.
Рис. Применение новых вспомогательных HTML-функций
<% using (Html.BeginForm()) { %>
<%= Html.ValidationSummary(true, "Account creation was unsuccessful. " +
"Please correct the errors and try again.") %>
<div>
<fieldset>
<legend>Account Information</legend>
<div class="editor-label">
<%= Html.LabelFor(m => m.UserName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.UserName) %>
<%= Html.ValidationMessageFor(m => m.UserName) %>
</div>
Собственные атрибуты проверки
Не все атрибуты проверки класса RegisterModel содержатся в сборке аннотаций данных от Microsoft. [PropertiesMustMatch] и [ValidatePasswordLength] являются собственными атрибутами и определены в том же файле AccountModel.cs, что и класс RegisterModel. Если вам нужно лишь предоставить собственное правило проверки, то незачем волноваться о собственных провайдерах метаданных или классах метаданных. Достаточно наследования от абстрактного класса ValidationAttribute и реализации метода IsValid. Реализация атрибута ValidatePasswordLength представлена на рис. 5.
Рис. Реализация атрибута ValidatePasswordLength
[AttributeUsage(AttributeTargets.Field |
AttributeTargets.Property,
AllowMultiple = false,
Inherited = true)]
public sealed class ValidatePasswordLengthAttribute
: ValidationAttribute
{
private const string _defaultErrorMessage =
"’{0}’ must be at least {1} characters long.";
private readonly int _minCharacters =
Membership.Provider.MinRequiredPasswordLength;
public ValidatePasswordLengthAttribute()
: base(_defaultErrorMessage)
{
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture,
ErrorMessageString,
name, _minCharacters);
}
public override bool IsValid(object value)
{
string valueAsString = value as string;
return (valueAsString != null &&
valueAsString.Length >= _minCharacters);
}
}
PropertiesMustMatch — еще один отличный пример атрибута проверки, который вы можете применять на уровне класса для проверок значений свойств в комплексе.
Проверка на клиентской стороне
Проверка RegisterModel, которую мы до сих пор рассматривали, полностью выполняется на сервере. К счастью, на клиенте тоже можно выполнять проверку. Я стараюсь по возможности всегда использовать проверку на клиентской стороне, так как именно она дает быструю обратную связь с пользователем и при этом не создает лишней нагрузки на сервер. Однако соответствующая логика на серверной стороне все равно должна присутствовать на случай, если у кого-то не включена поддержка выполнения сценариев в браузере (или он намеренно пытается посылать неправильные данные на сервер).
Включение проверки на клиентской стороне осуществляется в два этапа. На первом этапе нужно убедиться, что представление содержит необходимые сценарии проверки. Все нужные вам сценарии хранятся в папке Scripts нового MVC-приложения. Сценарий MicrosoftAjax.js является основой библиотек Microsoft AJAX и первым, который вы должны включить в свое приложение. Второй сценарий — MicrosoftMvcValidation.js. Для включения сценариев я обычно добавляю в эталонную страницу своего MVC-приложения ContentPlaceHolder:
<head runat="server">
<title><asp:ContentPlaceHolder ID="TitleContent" runat=
"server" /></title>
<link href="../../Content/Site.css" rel="stylesheet" type=
"text/css" />
<asp:ContentPlaceHolder ID="Scripts" runat="server">
</asp:ContentPlaceHolder>
</head>
Затем в представление можно включить необходимые ему сценарии с помощью элемента управления Content. Код, показанный ниже, проверяет наличие нужных сценариев проверки:
<asp:Content ContentPlaceHolderID="Scripts" runat="server">
<script src="../../Scripts/MicrosoftAjax.js"
type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js"
type="text/javascript"></script>
</asp:Content>
Второй этап — вызов вспомогательного HTML-метода EnableClientValidation в представлении, где необходимы поддержка проверки. Обязательно вызывайте этот метод до использования вспомогательного HTML-метода BeginForm:
<%
Html.EnableClientValidation();
using (Html.BeginForm())
{
%>
<!-- the rest of the form ... -->
<% } %>
Заметьте, что логика проверки на клиентской стороне работает только со встроенными атрибутами проверки. В случае представления Register это означает, что вы сможете проверить наличие значений в обязательных полях, но вам не удастся проверить длину пароля или совпадение содержимого двух полей ввода паролей. К счастью, вы легко можете добавить свою логику проверки на JavaScript, которая подключается к инфраструктуре проверки ASP.NET MVC JavaScript. Детальное описание предоставил Фил Хаак (Phil Haack) в статье «ASP.NET MVC 2 Custom Validation», опубликованной в его блоге по ссылке haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.
Ну и пара слов в заключение. Как вы убедились, встроенная поддержка распространенных вариантов проверки является грандиозным новшеством в ASP.NET MVC 2. Теперь не только легко добавлять правила проверки через атрибуты объекта модели, но и сами средства проверки стали гибкими и расширяемыми. Начинайте использовать эти средства уже в следующем своем приложении ASP.NET MVC для экономии времени и уменьшения объема кода.
Скотт Аллен (Scott Allen) — технический сотрудник Pluralsight и учредитель OdeToCode. С ним можно связаться по адресу scott@OdeToCode.com или через его блог odetocode.com/blogs/scott.
Выражаю благодарность за рецензирование этой статьи эксперту Брэду Уилсону (Brad Wilson).Брэд Уилсон (Brad Wilson)