Итерация 3. Добавление проверки форм (C#)
от Майкрософт
В третьей итерации мы добавим базовую проверку формы. Мы запрещаем пользователям отправлять форму без заполнения обязательных полей формы. Мы также проверяем адреса электронной почты и номера телефонов.
Создание приложения MVC для управления контактами ASP.NET (C#)
В этой серии учебников мы создадим все приложение управления контактами от начала до конца. Приложение Диспетчер контактов позволяет хранить контактные данные ( имена, номера телефонов и адреса электронной почты) для списка людей.
Мы создаем приложение с помощью нескольких итераций. С каждой итерацией мы постепенно улучшаем приложение. Цель этого подхода с несколькими итерациями — дать возможность понять причину каждого изменения.
Итерация 1. Создание приложения. В первой итерации мы создадим диспетчер контактов самым простым способом. Добавлена поддержка базовых операций с базами данных: создание, чтение, обновление и удаление (CRUD).
Итерация 2. Сделайте приложение красивым. В этой итерации мы улучшаем внешний вид приложения, изменив ASP.NET по умолчанию представление MVC master страницу и каскадную таблицу стилей.
Итерация 3. Добавление проверки формы. В третьей итерации мы добавим базовую проверку формы. Мы запрещаем пользователям отправлять форму без заполнения обязательных полей формы. Мы также проверяем адреса электронной почты и номера телефонов.
Итерация 4. Сделайте приложение слабосвязанным. В этой четвертой итерации мы воспользуемся преимуществами нескольких шаблонов проектирования программного обеспечения, чтобы упростить обслуживание и изменение приложения Диспетчера контактов. Например, мы рефакторинг приложения для использования шаблона репозитория и шаблона внедрения зависимостей.
Итерация 5. Создание модульных тестов. В пятой итерации мы упростим обслуживание и изменение приложения, добавив модульные тесты. Мы имитируем классы модели данных и создаем модульные тесты для наших контроллеров и логики проверки.
Итерация 6. Использование разработки на основе тестирования. В этой шестой итерации мы добавим новые функции в приложение, сначала написав модульные тесты и написав код для модульных тестов. В этой итерации мы добавим группы контактов.
Итерация 7. Добавление функциональных возможностей Ajax. В седьмой итерации мы повышаем скорость реагирования и производительность приложения, добавляя поддержку Ajax.
Эта итерация
Во второй итерации приложения Диспетчера контактов мы добавим базовую проверку формы. Мы запрещаем пользователям отправлять контакт, не вводя значения для обязательных полей формы. Мы также проверяем номера телефонов и адреса электронной почты (см. рис. 1).
Рис. 01. Форма с проверкой (щелкните для просмотра полноразмерного изображения)
В этой итерации мы добавим логику проверки непосредственно в действия контроллера. Как правило, это не рекомендуемый способ добавления проверки в приложение ASP.NET MVC. Лучший подход заключается в том, чтобы разместить логику проверки приложения в отдельном уровне служб. В следующей итерации мы рефакторинг приложения Диспетчера контактов, чтобы сделать приложение более пригодным для обслуживания.
В этой итерации, чтобы упростить процесс, мы напишем весь код проверки вручную. Вместо того, чтобы самостоятельно писать код проверки, мы могли бы воспользоваться преимуществами платформы проверки. Например, можно использовать блок приложения проверки корпоративной библиотеки Майкрософт (VAB) для реализации логики проверки для приложения ASP.NET MVC. Дополнительные сведения о блоке приложения проверки см. в следующих разделах:
http://msdn.microsoft.com/library/dd203099.aspx
Добавление проверки в представление создания
Начнем с добавления логики проверки в представление Создание. К счастью, так как мы создали представление "Создать" с помощью Visual Studio, представление "Создание" уже содержит всю необходимую логику пользовательского интерфейса для отображения сообщений проверки. Представление Создание содержится в листинге 1.
Листинг 1. \Views\Contact\Create.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Create</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<fieldset class="fields">
<legend>Create New Contact</legend>
<p>
<label for="FirstName">First Name:</label>
<%= Html.TextBox("FirstName") %>
<%= Html.ValidationMessage("FirstName", "*") %>
</p>
<p>
<label for="LastName">Last Name:</label>
<%= Html.TextBox("LastName") %>
<%= Html.ValidationMessage("LastName", "*") %>
</p>
<p>
<label for="Phone">Phone:</label>
<%= Html.TextBox("Phone") %>
<%= Html.ValidationMessage("Phone", "*") %>
</p>
<p>
<label for="Email">Email:</label>
<%= Html.TextBox("Email") %>
<%= Html.ValidationMessage("Email", "*") %>
</p>
<p class="submit">
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
</asp:Content>
Обратите внимание на вызов вспомогательного метода Html.ValidationSummary(), который отображается непосредственно над html-формой. Если имеются сообщения об ошибках проверки, этот метод отображает сообщения проверки в маркированный список.
Кроме того, обратите внимание на вызовы Html.ValidationMessage(), которые отображаются рядом с каждым полем формы. Вспомогающая функция ValidationMessage() отображает отдельное сообщение об ошибке проверки. В листинге 1 при возникновении ошибки проверки отображается звездочка.
Наконец, вспомогательное средство Html.TextBox() автоматически отрисовывает класс каскадной таблицы стилей при возникновении ошибки проверки, связанной со свойством, отображаемым вспомогательной службой. Вспомогатель Html.TextBox() отображает класс с именем input-validation-error.
При создании нового приложения ASP.NET MVC в папке Содержимое автоматически создается таблица стилей с именем Site.css. Эта таблица стилей содержит следующие определения для классов CSS, связанных с появлением сообщений об ошибках проверки:
.field-validation-error
{
color: #ff0000;
}
.input-validation-error
{
border: 1px solid #ff0000;
background-color: #ffeeee;
}
.validation-summary-errors
{
font-weight: bold;
color: #ff0000;
}
Класс field-validation-error используется для оформления выходных данных, отображаемых вспомогательным методом Html.ValidationMessage(). Класс input-validation-error используется для оформления текстового поля (входных данных), отображаемого вспомогательным элементом Html.TextBox(). Класс validation-summary-errors используется для оформления неупорядоченного списка, отображаемого вспомогательным элементом Html.ValidationSummary().
Примечание
Вы можете изменить классы таблиц стилей, описанные в этом разделе, чтобы настроить внешний вид сообщений об ошибках проверки.
Добавление логики проверки в действие создания
Сейчас в представлении Создание никогда не отображаются сообщения об ошибках проверки, так как мы не написали логику для создания сообщений. Чтобы отобразить сообщения об ошибках проверки, необходимо добавить сообщения об ошибках в ModelState.
Примечание
Метод UpdateModel() автоматически добавляет сообщения об ошибках в ModelState при возникновении ошибки при присвоении значения поля формы свойству. Например, при попытке назначить строку apple свойству BirthDate, которое принимает значения DateTime, метод UpdateModel() добавляет ошибку в ModelState.
Измененный метод Create() в листинге 2 содержит новый раздел, который проверяет свойства класса Contact перед вставкой нового контакта в базу данных.
Листинг 2. Controllers\ContactController.cs (создание с проверкой)
//
// POST: /Contact/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
// Validation logic
if (contactToCreate.FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "First name is required.");
if (contactToCreate.LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "Last name is required.");
if (contactToCreate.Phone.Length > 0 && !Regex.IsMatch(contactToCreate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.");
if (contactToCreate.Email.Length > 0 && !Regex.IsMatch(contactToCreate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.");
if (!ModelState.IsValid)
return View();
// Database logic
try
{
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
В разделе validate применяются четыре отдельных правила проверки:
- Свойство FirstName должно иметь длину больше нуля (и оно не может состоять исключительно из пробелов).
- Свойство LastName должно иметь длину больше нуля (и оно не может состоять исключительно из пробелов).
- Если свойство Phone имеет значение (имеет длину больше 0), то свойство Phone должно соответствовать регулярному выражению.
- Если свойство Email имеет значение (имеет длину больше 0), то свойство Email должно соответствовать регулярному выражению.
При нарушении правила проверки в ModelState с помощью метода AddModelError() добавляется сообщение об ошибке. При добавлении сообщения в ModelState укажите имя свойства и текст сообщения об ошибке проверки. Это сообщение об ошибке отображается в представлении вспомогательными методами Html.ValidationSummary() и Html.ValidationMessage().
После выполнения правил проверки проверяется свойство IsValid объекта ModelState. Свойство IsValid возвращает значение false при добавлении сообщений об ошибках проверки в ModelState. Если проверка завершается сбоем, форма создания повторно отображается с сообщениями об ошибках.
Примечание
Я получил регулярные выражения для проверки номера телефона и адреса электронной почты из репозитория регулярных выражений по адресу http://regexlib.com
Добавление логики проверки в действие "Изменить"
Действие Edit() обновляет объект Contact. Действие Edit() должно выполнять ту же проверку, что и действие Create(). Вместо того, чтобы дублировать один и тот же код проверки, следует выполнить рефакторинг контроллера Contact, чтобы действия Create() и Edit() вызывали один и тот же метод проверки.
Измененный класс контроллера Contact содержится в листинге 3. Этот класс имеет новый метод ValidateContact(), который вызывается в действиях Create() и Edit().
Листинг 3. Controllers\ContactController.cs
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models;
namespace ContactManager.Controllers
{
public class ContactController : Controller
{
private ContactManagerDBEntities _entities = new ContactManagerDBEntities();
protected void ValidateContact(Contact contactToValidate)
{
if (contactToValidate.FirstName.Trim().Length == 0)
ModelState.AddModelError("FirstName", "First name is required.");
if (contactToValidate.LastName.Trim().Length == 0)
ModelState.AddModelError("LastName", "Last name is required.");
if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.");
if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.");
}
public ActionResult Index()
{
return View(_entities.ContactSet.ToList());
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
{
// Validation logic
ValidateContact(contactToCreate);
if (!ModelState.IsValid)
return View();
// Database logic
try
{
_entities.AddToContactSet(contactToCreate);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
public ActionResult Edit(int id)
{
var contactToEdit = (from c in _entities.ContactSet
where c.Id == id
select c).FirstOrDefault();
return View(contactToEdit);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Contact contactToEdit)
{
ValidateContact(contactToEdit);
if (!ModelState.IsValid)
return View();
try
{
var originalContact = (from c in _entities.ContactSet
where c.Id == contactToEdit.Id
select c).FirstOrDefault();
_entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
public ActionResult Delete(int id)
{
var contactToDelete = (from c in _entities.ContactSet
where c.Id == id
select c).FirstOrDefault();
return View(contactToDelete);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Contact contactToDelete)
{
try
{
var originalContact = (from c in _entities.ContactSet
where c.Id == contactToDelete.Id
select c).FirstOrDefault();
_entities.DeleteObject(originalContact);
_entities.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
Итоги
В этой итерации мы добавили базовую проверку формы в приложение Диспетчера контактов. Наша логика проверки запрещает пользователям отправлять новый контакт или редактировать существующий контакт, не предоставляя значения для свойств FirstName и LastName. Кроме того, пользователи должны указать допустимые номера телефонов и адреса электронной почты.
В этой итерации мы добавили логику проверки в приложение Диспетчера контактов самым простым способом. Однако смешивание логики проверки с логикой контроллера создаст проблемы в долгосрочной перспективе. Наше приложение будет сложнее поддерживать и изменять со временем.
В следующей итерации мы будем выполнять рефакторинг логики проверки и логики доступа к базе данных из контроллеров. Мы воспользуемся преимуществами нескольких принципов проектирования программного обеспечения, чтобы создать более слабо связанное и более доступное для обслуживания приложение.