Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Валидация модели

В предыдущей главе мы рассмотрели, как MVC Framework создает объекты моделей из запросов HTTP с помощью механизма связывания данных. Тогда мы предполагали, что данные, предоставленные пользователями, являются действительными. На самом деле, пользователи будут часто вводить данные, с которыми мы не сможем работать, что подводит нас к теме этой главы - валидация моделей.

Валидация моделей - это подтверждение того, что полученные данные пригодны для связывания с моделью, и, если это не так, отображение пользователю сообщения с описанием проблемы.

Первая часть процесса - проверка полученных данных - позволяет сохранить целостность доменной модели. Фильтруя данные, которые не имеют смысла в контексте нашего домена, мы предотвращаем возникновение странных и нежелательных состояний в нашем приложении. Вторая часть - помощь пользователю в исправлении ошибки - не менее важна. Если мы не предоставим пользователю информацию и инструменты, необходимые для нормального взаимодействия с нашим приложением, то собьем их с толку и вызовем их недовольство. Если это общедоступное приложение, то пользователи просто перестанут его использовать. Если это корпоративное приложение, то рабочий процесс будет затруднен. Ни то, ни другое для нас не желательно. К счастью, MVC Framework предоставляет расширенную поддержку валидации моделей. Мы рассмотрим, как использовать ее базовые функции, а затем продемонстрируем продвинутые техники для тонкой настройки процесса валидации.

Создание проекта для примера

Прежде чем начать, мы создадим простое приложение MVC, к которому будем применять различные техники валидации моделей. Мы создали новый проект MVC под названием ModelValidation на шаблоне Basic, а также новый класс модели под названием Appointment.cs, который показан в листинге 23-1.

Листинг 23-1: Класс модели Appointment

using System;

using System.ComponentModel.DataAnnotations; namespace ModelValidation.Models

{

public class Appointment

{

public string ClientName { get; set; } [DataType(DataType.Date)]

public DateTime Date { get; set; } public bool TermsAccepted { get; set; }

}

}

В классе модели Appointment мы определили три свойства и указали с помощью атрибута DataType, что свойство Date должно быть выражено в формате даты без компонента времени.

Для этого примера мы также создали контроллер Home и определили методы действий, которые работают с классом модели Appointment, как показано в листинге 23-2.

Листинг 23-2: Контроллер Home в проекте ModelValidation

using System;

using System.Web.Mvc;

591

using ModelValidation.Models;

namespace ModelValidation.Controllers

{

public class HomeController : Controller

{

public ViewResult MakeBooking()

{

return View(new Appointment {Date = DateTime.Now});

}

[HttpPost]

public ViewResult MakeBooking(Appointment appt)

{

//statements to store new Appointment in a

//repository would go here in a real project return View("Completed", appt);

}

}

}

Как и в предыдущих главах, мы определили две версии метода действия MakeBooking. В этой главе нас интересует версия с атрибутом HttpPost, так как именно в ней будет применяться связывание данных для создания объекта параметра Appointment.

Обратите внимание, что в реальном приложении мы указали бы в комментариях, где находятся операторы для сохранения информации об объекте Appointment, который будет создан механизмом связывания. В этом примере мы не собираемся создавать хранилище, потому что хотим сосредоточиться на процессах связывания данных и валидации. Однако, важно помнить, что цель валидации модели - предотвратить сохранение в хранилище нежелательных или бессмысленных данных и возникновение проблем (при попытке их сохранения или, в дальнейшем, обработки).

Чтобы завершить наш пример приложения, создадим пару представлений для работы с методами действий в папке /Views/Home. В листинге 23-3 показано содержимое файла MakeBooking.cshtml, который содержит форму для создания новой записи на прием.

Листинг 23-3: Содержимое файла MakeBooking.cshtml

@model ModelValidation.Models.Appointment

@{

ViewBag.Title = "Make A Booking";

}

<h4>Book an Appointment</h4>

@using (Html.BeginForm())

{

<p>Your name: @Html.EditorFor(m => m.ClientName)</p> <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>

<p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" />

}

Когда форма будет отправлена обратно к приложению, метод действия MakeBooking отобразит подробную информацию о созданной пользователем записи с помощью представления Completed.cshtml, которое показано в листинге 23-4.

Листинг 23-4: Содержимое файла Completed.cshtml

@model ModelValidation.Models.Appointment

592

@{

ViewBag.Title = "Completed";

}

<h4>Your appointment is confirmed</h4>

<p>Your name is: <b>@Html.DisplayFor(m => m.ClientName)</b></p>

<p>The date of your appointment is: <b>@Html.DisplayFor(m => m.Date)</b></p>

Как вы уже поняли, наш пример для этой главы основан на создании записей на прием. Чтобы увидеть, как это работает, запустите приложение и перейдите по ссылке Home/MakeBooking. Введите реквизиты в форму и нажмите кнопку Submit, чтобы отправить данные на сервер; он выполнит связывание данных и создаст объект Appointment, информация о котором затем будет отображена в представлении Completed.cshtml, как показано на рисунке 23 -1.

Рисунок 23-1: Используем пример приложения

Сейчас наше приложение будет принимать любые данные, которые отправит пользователь, но, чтобы сохранить целостность нашего приложения и доменной модели, мы будем требовать выполнения трех условий, прежде чем принять отправленный пользователем объект Appointment:

Пользователь должен указать имя.

Пользователь должен указать дату (в формате mm/dd/yyyy), которая относится к будущему.

Пользователь должен отметить чекбокс, чтобы принять условия пользовательского соглашения.

Валидация модели – это процесс проверки соблюдения этих требований. В следующих разделах мы покажем вам различные техники для проверки указанных пользователем данных и предоставления обратной связи в тех случаях, когда мы не можем использовать полученные данные.

Явная валидация модели

Наиболее очевидный способ валидации модели – проверить данные в соответствующем методе действия. В листинге 23-5 показано, как мы добавили явную проверку для каждого свойства, определенного в классе Appointment, в HttpPost-версии метода действия MakeBooking.

Листинг 23-5: Осуществляем явную валидацию модели

[HttpPost]

public ViewResult MakeBooking(Appointment appt)

593

{

if (string.IsNullOrEmpty(appt.ClientName))

{

ModelState.AddModelError("ClientName", "Please enter your name");

}

if (ModelState.IsValidField("Date") && DateTime.Now > appt.Date)

{

ModelState.AddModelError("Date", "Please enter a date in the future");

}

if (!appt.TermsAccepted)

{

ModelState.AddModelError("TermsAccepted", "You must accept the terms");

}

if (ModelState.IsValid)

{

//statements to store new Appointment in a

//repository would go here in a real project return View("Completed", appt);

}

else

{

return View();

}

}

Мы проверяем значения, которые механизм связывания присвоил свойствам объекта параметра, и регистрируем ошибки, которые находим в свойстве ModelState (его наш контроллер наследует от базового класса). Например, свойство ClientName мы проверяем таким образом:

if (string.IsNullOrEmpty(appt.ClientName)) { ModelState.AddModelError("ClientName", "Please enter your name");

}

Мы хотим, чтобы пользователь ввел значение для этого свойства, поэтому используем статический метод string.IsNullOrEmpty, чтобы его проверить. Если мы не получили значение, то с помощью метода ModelState.AddModelError указываем имя свойства, с которым возникла проблема (ClientName), и сообщение, которое будет показано пользователю, чтобы помочь ему исправить проблему (Please enter your name).

С помощью свойства ModelState.IsValidField мы можем проверить, смог ли механизм связывания присвоить значение. Мы применяем его в свойстве Date, чтобы убедиться, что механизм связывания смог проанализировать значение, полученное от пользователя; нет никакого смысла выполнять дополнительные проверки или регистрировать дополнительные ошибки, если значение не могло быть выделено из данных запроса.

После того как мы провели валидацию всех свойств в объекте модели, мы проверяем свойство ModelState.IsValid на наличие ошибок. Если во время проверок мы вызвали метод Model.State.AddModelError, или у механизма связывания возникли проблемы с созданием объекта Appointment, то этот метод возвращает true.

if (ModelState.IsValid)

{

//statements to store new Appointment in a

//repository would go here in a real project return View("Completed", appt);

}

else

594

{

return View();

}

Если в свойстве IsValid нет ошибок, то наш объект Appointment является действительным, и мы можем визуализировать представление Completed.cshtml (и, в реальном проекте, сохранить объект Appointment в хранилище). Если свойство IsValue возвращает false, то мы знаем, что у нас есть проблема, и вызываем метод View, который визуализирует представление по умолчанию.

Отображаем ошибки валидации пользователю

Может показаться странным, что мы обрабатываем ошибки валидации с помощью метода View, но шаблонные вспомогательные методы, которые мы использовали для создания элементов ввода в

представлении MakeBooking.cshtml, проверяют модель представления на наличие ошибок валидации.

Если в свойстве появилась ошибка, вспомогательный метод добавляет к элементу ввода класс CSS

под названием input-validation-error. Файл ~/Content/Site.css содержит определение по умолчанию для этого стиля, которое выглядит следующим образом:

.input-validation-error { border: 1px solid #f00; background-color: #fee;

}

Он устанавливает красную рамку и розовый фон для элемента, в котором есть ошибка. Чтобы проверить работу явной валидации, запустите приложение, перейдите по ссылке /Home/MakeBooking и нажмите кнопку Make Booking, не вводя данные в форму. Результат показан на рисунке 23-2.

Подсказка

Если вы хотите написать собственные вспомогательные методы, которые поддерживают валидацию, изучите исходный код класса

System.Mvc.Web.Html.InputExtensions, чтобы понять, как это сделаеть.

Вкратце скажем, что класс System.Web.Mvc.HtmlHelper определяет метод GetModelStateValue, который позволяет проверить, есть ли ошибки валидации для

конкретного свойства. Есть и дополнительные методы, которые позволяют легко получить необходимый CSS и сообщения об ошибках для создания предупреждения пользователю.

Рисунок 23-2: Поля с ошибками подсвечены

595

Применяем стили к чекбоксам

Некоторые браузеры, в том числе Chrome и Firefox, игнорируют стили, примененные к чекбоксам, в результате чего элементы формы будут выглядеть непоследовательно. Чтобы решить эту проблему, можно заменить шаблон Boolean для элементов editor пользовательским шаблоном

~/Views/Shared/EditorTemplates/Boolean.cshtml и заключить чекбокс в элемент div. Мы используем следующий шаблон, который вы можете приспособить для своего приложения:

Этот шаблон поместит чекбокс в элемент div, к которому будет применен стиль input-validation- error, если возникнут какие-либо ошибки модели, связанные с тем свойством, к которому был применен шаблон. Вы можете узнать больше о замене шаблонов editor в главе 20.

Если вы отправите форму без данных, ошибки будут подсвечены для свойств ClientName и TermsAccepted. Значение по умолчанию для свойства Date является действительным, поэтому ошибка валидации не возникает.

Пользователь не увидит представление Completed.cshtml, пока не отправит форму с данными, которые могут быть проанализированы браузером модели (model browser), который передает операторы явной валидации в метод действия MakeBooking. В противном случае отправка формы будет визализировать представление MakeBooking.cshtml с текущими ошибками валидации.

Отображение сообщений валидации

CSS стили, которые применяются шаблонными вспомогательными методами к элементам ввода, указывают на проблему с полем, но не сообщают пользователю, в чем эта проблема заключается. К счастью, для этого существуют другие вспомогательные методы. В листинге 23-6 показан один из таких методов, который мы применили к представлению MakeBooking (так как именно оно демонстрирует ошибки валидации пользователю).

Листинг 23-6: Используем вспомогательный метод ValidationSummary

<h4>Book an Appointment</h4>

@using (Html.BeginForm())

{

@Html.ValidationSummary()

<p>Your name: @Html.EditorFor(m => m.ClientName)</p> <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>

<p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" />

}

Вспомогательный метод Html.ValidationSummary добавляет сообщение об ошибках валидации, которые были зарегистрированы на отображаемой пользователю странице. Если ошибок нет, то вспомогательный метод не генерирует HTML. На рисунке 23-3 показан результат применения этого метода. Мы очистили поля ввода и отправили форму.

596

Рисунок 23-3: Отображение сообщения валидации

Примечание

Для свойства Date мы используем значения в американском формате даты month/day/year. Если вы находитесь в другой стране, то можете ввести допустимые даты в местном формате (например, day/month/year, который

широко используется в Европе) или добавить для этого проекта строку

<globalization culture="en-US" uiCulture="en-US"/> в элемент system.web в

файле Web.config, чтобы приложение использовало формат даты US.

В сообщении валидации отображается информация об ошибках, которые были зарегистрированы в ModelState в методе действия MakeBooking. Этот вспомогательный метод генерирует следующий

HTML:

<div class="validation-summary-errors" data-valmsg-summary="true"> <ul>

<li>Please enter your name</li> <li>Please enter a date in the future</li> <li>You must accept the terms</li>

</ul>

</div>

Ошибки выводятся в виде списка в элементе div, к которому применен стиль validation-summary- errors. Этот стиль определен в ~/Content/Site.css и по мере необходимости может быть изменен. По умолчанию стиль выделяет текст сообщений об ошибках полужирным и красным:

.validation-summary-errors { font-weight: bold;

color: #f00;

}

Для метода ValidationSummary существует несколько перегруженных версий; наиболее полезные из них показаны в таблице 23-1. Некоторые из этих версий позволяют указать, что нужно отображать только ошибки уровня модели (model-level errors). Ошибки, которые до сих пор были зарегистрированы в ModelState, относятся к уровню свойства (property-level errors) и означают, что для данного свойства указано недействительное значение; если его изменить, проблема будет решена.

597

Таблица 23-1: Полезные перегруженные версии вспомогательного метода ValidationSummary

Перегруженный метод

Описание

 

 

Html.ValidationSummary()

Генерирует сообщения для всех ошибок валидации.

 

 

 

Если параметр bool содержит true, то отображаются только

Html.ValidationSummary(bool)

ошибки уровня модели (см. пояснение после таблицы). Если

 

параметр содержит false, то отображаются все ошибки.

Html.ValidationSummary(string)

Html.ValidationSummary(bool,

string)

Отображает сообщение (содержащееся в параметре string) перед сообщениями обо всех ошибках валидации.

Отображает сообщение перед сообщениями об ошибках валидации. Если параметр bool содержит true, то отображаются только ошибки уровня модели.

С другой стороны, ошибки уровня модели можно использовать, если проблемы возникают из-за взаимодействия двух и более значений свойств. Простой пример: давайте представим, что клиенту по имени Джо нельзя записываться на прием по понедельникам. В листинге 23-7 показано, как для этого правила можно создать явное условие валидации в методе действия MakeBooking и вывести сообщение об ошибке валидации уровня модели.

Листинг 23-7: Ошибка валидации уровня модели

[HttpPost]

public ViewResult MakeBooking(Appointment appt)

{

if (string.IsNullOrEmpty(appt.ClientName))

{

ModelState.AddModelError("ClientName", "Please enter your name");

}

if (ModelState.IsValidField("Date") && DateTime.Now > appt.Date)

{

ModelState.AddModelError("Date", "Please enter a date in the future");

}

if (!appt.TermsAccepted)

{

ModelState.AddModelError("TermsAccepted", "You must accept the terms");

}

if (ModelState.IsValidField("ClientName") && ModelState.IsValidField("Date") && appt.ClientName == "Joe" && appt.Date.DayOfWeek == DayOfWeek.Monday)

{

ModelState.AddModelError("", "Joe cannot book appointments on Mondays");

}

if (ModelState.IsValid)

{

//statements to store new Appointment in a

//repository would go here in a real project return View("Completed", appt);

}

else

{

return View();

}

}

598

Прежде чем проверить, пытается ли Джо записаться на прием в понедельник, мы подтверждаем с помощью метода ModelState.IsValidField, что значения свойств ClientName и Date

действительны.

Это означает, что мы не будем генерировать ошибку уровня модели, пока не проведем успешную валидацию предыдущих свойств. Чтобы зарегистрировать ошибку уровня модели, мы передаем пустую строку ("") в качестве первого параметра в метод ModelState.AddModelError, например:

ModelState.AddModelError("", "Joe cannot book appointments on Mondays");

Далее мы обновим файл MakeBooking.cshtml, чтобы использовать в нем версию вспомогательного метода ValidationSummary, которая принимает параметр bool и будет отображать только ошибки уровня модели, как показано в листинге 23-8.

Листинг 23-8: Обновляем представление MakeBooking.cshtml, чтобы отображать только ошибки уровня модели

@model ModelValidation.Models.Appointment

@{

ViewBag.Title = "Make A Booking";

}

<h4>Book an Appointment</h4>

@using (Html.BeginForm())

{

@Html.ValidationSummary(true)

<p>Your name: @Html.EditorFor(m => m.ClientName)</p> <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>

<p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" />

}

Результат этих изменений показан на рисунке 23-4, где мы ввели имя Джо и указали дату, которая выпадает на понедельник.

Рисунок 23-4: Отображение сообщения валидации для ошибок уровня модели

599

Как видно из рисунка, есть две ошибки валидации. Первая - это ошибка уровня модели, которая возникает, когда Джо пытается записаться на прием в понедельник. Вторая появляется из-за того, что не отмечен чекбокс для условий пользовательского соглашения. Так как в сообщении валидации мы отображаем только ошибки уровня модели, пользователь не увидит никаких сведений о второй проблеме.

Отображаем сообщения валидации уровня свойства

Мы отображаем только сообщения об ошибках уровня модели потому, что ошибки уровня свойства можно отображать рядом с соответствующими полями. Таким образом, мы не хотим дублировать сообщения, относящиеся к отдельным свойствам. В листинге 23-9 показано обновленное представление MakeBooking.cshtml, которое отображает ошибки уровня модели сверху и ошибки уровня свойства рядом с соответствующим полем ввода.

Листинг 23-9: Используем сообщения об ошибках для конкретного свойства

@model ModelValidation.Models.Appointment

@{

ViewBag.Title = "Make A Booking";

}

<h4>Book an Appointment</h4>

@using (Html.BeginForm())

{

@Html.ValidationSummary(true)

<p>@Html.ValidationMessageFor(m => m.ClientName)</p>

<p>Your name: @Html.EditorFor(m => m.ClientName)</p>

<p>@Html.ValidationMessageFor(m => m.Date)</p>

<p>Appointment Date: @Html.EditorFor(m => m.Date)</p>

<p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>

<p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p> <input type="submit" value="Make Booking" />

}

Вспомогательный метод Html.ValidationMessageFor отображает ошибки валидации для одного свойства модели. Результат его применения в представлении MakeBooking показан на рисунке 23-5.

Рисунок 23-5: Используем вспомогательный метод для отображения сообщений валидации для одного свойства

600

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]