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)