迭代 3 – 添加表单验证 (C#)
在第三次迭代中,我们添加了基本表单验证。 我们会阻止用户在未填写必填表单字段的情况下提交表单。 我们还验证电子邮件地址和电话号码。
生成联系人管理 ASP.NET MVC 应用程序 (C#)
在本系列教程中,我们将从头到尾构建整个联系人管理应用程序。 通过 Contact Manager 应用程序,可以存储联系人列表的联系人信息(姓名、电话号码和电子邮件地址)。
我们通过多次迭代生成应用程序。 每次迭代后,我们都会逐步改进应用程序。 此多迭代方法的目标是使你能够了解每次更改的原因。
迭代 #1 - 创建应用程序。 在第一次迭代中,我们将以最简单的方式创建联系人管理器。 添加了对基本数据库操作的支持:创建、读取、更新和删除 (CRUD) 。
迭代 #2 - 使应用程序外观美观。 在此迭代中,我们通过修改默认 ASP.NET MVC 视图母版页和级联样式表来改进应用程序的外观。
迭代 #3 - 添加表单验证。 在第三次迭代中,我们添加了基本表单验证。 我们会阻止用户在未填写必填表单字段的情况下提交表单。 我们还验证电子邮件地址和电话号码。
迭代 #4 - 使应用程序松散耦合。 在第四次迭代中,我们将利用多种软件设计模式,以便更轻松地维护和修改 Contact Manager 应用程序。 例如,我们将应用程序重构为使用存储库模式和依赖关系注入模式。
迭代 #5 - 创建单元测试。 在第五次迭代中,我们通过添加单元测试使应用程序更易于维护和修改。 我们将模拟数据模型类,并为控制器和验证逻辑生成单元测试。
迭代 #6 - 使用测试驱动开发。 在第六次迭代中,我们通过先编写单元测试,然后针对单元测试编写代码,向应用程序添加新功能。 在此迭代中,我们将添加联系人组。
迭代 #7 - 添加 Ajax 功能。 在第七次迭代中,我们通过添加对 Ajax 的支持来提高应用程序的响应能力和性能。
此迭代
在 Contact Manager 应用程序的第二次迭代中,我们添加了基本表单验证。 我们会阻止用户在未为必填表单域输入值的情况下提交联系人。 我们还验证电话号码和电子邮件地址 (见图 1) 。
图 01:具有验证 (的窗体单击以查看全尺寸图像)
在此迭代中,我们将验证逻辑直接添加到控制器操作。 通常,不建议使用此方法将验证添加到 ASP.NET MVC 应用程序。 更好的方法是将应用程序的验证逻辑放置在单独的 服务层中。 在下一次迭代中,我们将重构 Contact Manager 应用程序,使应用程序更易于维护。
在此迭代中,为了简单起见,我们手动编写所有验证代码。 我们可以利用验证框架,而不是自己编写验证代码。 例如,可以使用 Microsoft 企业库验证应用程序块 (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 表单正上方显示的 Html.ValidationSummary () 帮助程序方法的调用。 如果存在验证错误消息,则此方法在项目符号列表中显示验证消息。
此外,请注意每个窗体字段旁边显示的对 Html.ValidationMessage () 的调用。 ValidationMessage () 帮助程序显示单个验证错误消息。 对于列表 1,当出现验证错误时,将显示一个星号。
最后,当出现与帮助程序显示的属性关联的验证错误时,Html.TextBox () 帮助程序会自动呈现级联样式表类。 Html.TextBox () 帮助程序呈现名为 input-validation-error 的类。
创建新的 ASP.NET MVC 应用程序时,会自动在 Content 文件夹中创建名为 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”分配给接受 DateTime 值的 BirthDate 属性,则 UpdateModel () 方法会将错误添加到 ModelState。
列表 2 中修改的 Create () 方法包含一个新节,该节在将新联系人插入数据库之前验证 Contact 类的属性。
列表 2 - Controllers\ContactController.cs (Create with validation)
//
// 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();
}
}
验证部分强制实施四个不同的验证规则:
- FirstName 属性的长度必须大于零 (,并且不能仅包含空格)
- LastName 属性的长度必须大于零 (,并且不能仅包含空格)
- 如果 Phone 属性的值 (长度大于 0) 则 Phone 属性必须与正则表达式匹配。
- 如果 Email 属性的值 (长度大于 0) 则Email属性必须与正则表达式匹配。
如果存在验证规则冲突,则会借助 AddModelError () 方法将错误消息添加到 ModelState。 将消息添加到 ModelState 时,需要提供属性的名称和验证错误消息的文本。 此错误消息由 Html.ValidationSummary () 和 Html.ValidationMessage () 帮助程序方法显示在视图中。
执行验证规则后,将检查 ModelState 的 IsValid 属性。 将任何验证错误消息添加到 ModelState 时,IsValid 属性返回 false。 如果验证失败,则会重新显示“创建”窗体并显示错误消息。
注意
我从正则表达式存储库获取了用于验证电话号码和电子邮件地址的正则表达式 http://regexlib.com
将验证逻辑添加到编辑操作
编辑 () 操作更新联系人。 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();
}
}
}
}
总结
在此迭代中,我们向 Contact Manager 应用程序添加了基本表单验证。 我们的验证逻辑阻止用户提交新联系人或编辑现有联系人,而不提供 FirstName 和 LastName 属性的值。 此外,用户必须提供有效的电话号码和电子邮件地址。
在此迭代中,我们以最简单的方式向 Contact Manager 应用程序添加了验证逻辑。 但是,将验证逻辑混合到控制器逻辑中会长期给我们造成问题。 随着时间的推移,我们的应用程序将更难维护和修改。
在下一次迭代中,我们将重构控制器中的验证逻辑和数据库访问逻辑。 我们将利用多个软件设计原则,使我们能够创建一个更松散耦合、更易于维护的应用程序。