在这个系列中,Stephen Walther将演示如何通过ASP.NET MVC framework结合单元测试、TDD、Ajax、软件设计原则及设计模式创建一个完整的Contact Manager应用。本系列共七个章节,也是七次迭代过程。本人将陆续对其进行翻译并发布出来,希望能对学习ASP.NET MVC 的各位有所帮助。由于本人也是个MVC菜鸟,且E文水平亦是平平,文中如有疏漏敬请见谅。

注:为保证可读性,文中Controller、View、Model、Route、Action等ASP.NET MVC核心单词均未翻译。

迭代3 - 验证表单

这是Contact Manager的第三次迭代,在这次迭代中我们将为Contact Manager添加基本的表单验证。如果用户填写的表单不完整,我们将阻止其表单的提交。另外我们还要验证电话号码和电子邮件地址的合法性。(图1)

[翻译-ASP.NET MVC]Contact Manager开发之旅迭代3 - 验证表单_迭代

图1

本次迭代中,我们将验证逻辑直接写在controller的action中,不过这并不是ASP.NET MVC应用所推荐的方式。更好的办法是将这些验证逻辑布置到另外的service层中。下一次迭代的时候我们将重构Contact Manager应用,使其更易维护。

为了让本文看起来直观些,我们将在本次迭代中手写所有的验证代码。当然我们也可以利用某些现成的验证框架来实现自动生成这些验证代码。比如你可以使用Microsoft Enterprise Library Validation Application Block (VAB)来实现ASP.NET MVC的验证逻辑。

为Create View添加验证规则

现在就让我们开始为Create view添加验证规则吧。在前两次迭代中,我们已经通过VS生成了Create view,拖VS的福我们的Create view已经包含了页面中显示验证消息所需的所有逻辑。Create view如下:


<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %>  <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">      <h2>Create</h2>      <%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>      <% using (Html.BeginForm()) {%>          <fieldset>             <legend>Fields</legend>             <p>                 <label for="FirstName">FirstName:</label>                 <%= Html.TextBox("FirstName") %>                 <%= Html.ValidationMessage("FirstName", "*") %>             </p>             <p>                 <label for="LastName">LastName:</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>                 <input type="submit" value="Create" />             </p>         </fieldset>      <% } %>      <div>         <%=Html.ActionLink("Back to List", "Index") %>     </div>  </asp:Content>

注意紧靠HTML表单顶部的Html.ValidationSummary()这个helper方法。如果存在验证产生的错误信息,这个方法将使用无序列表呈现这些消息。

另外,每个表单对象后面调用的Html.ValidationMessage()方法会显示对应这个表单对象所产生的验证错误消息。比如上面所示代码中的情况下,如果存在验证错误,那么这里将会显示一个*号。

最后,如果存在验证错误,Html.TextBox()方法会自动为相关表单对象添加一个名为input-validation-error 的样式

当你新建一个ASP.NET MVC应用程序的时候,Content文件夹中默认生成的Site.css中便包含了这些验证相关的样式定义:

/* messages -------------*/  div.information, div.error, div.success, ul.validation-summary-errors {     margin:1em 0;     padding:1em; }  div.information {     color:#C60;     background-color:#FF9;     border:1px solid #F90; }  div.error, ul.validation-summary-errors {     color: #F00;     background-color:#C99;     border:1px solid #900; }  div.success {     color: #060;     background-color:#9C9;     border:1px solid #060; }  .input-validation-error {     border: 1px solid #ff0000;     background-color: #ffeeee; }


field-validation-error是用来定义Html.ValidationMessage()呈现时的样式,input-validation-error用来定义textbox(input)的样式,而Vaidation-summary-errors则用来定义Html.ValidationSummary()方法呈现的无序列表的样式。

你可以通过修改这些默认的样式规则从而美化和自定义这些验证错误消息的表现。

为Create Action添加验证规则

到目前为止,Create view不会显示任何的验证错误信息。因为我们尚未编写其生成消息的逻辑规则。这里你需要向ModelState添加错误消息。

指定的表单对象的值与对应属性发生验证错误时,UpdateModel()方法将自动添加错误消息到ModelState中。例如,如果你打算将“apple”作为BirthDate属性的值但是该属性只接受DateTime类型,则UpdateModel()方法会添加一个错误至ModelState中。

修改后的Create()方法代码如下,我们其添加了在新联系人插入数据库前验证联系人属性的相关代码部分:

//         // POST: /Home/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.Trim().Length == 0 || !Regex.IsMatch(contactToCreate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))                 ModelState.AddModelError("Phone", "Invalid phone number");             if (contactToCreate.Email.Trim().Length == 0 || !Regex.IsMatch(contactToCreate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))                 ModelState.AddModelError("Email", "Invalid email address");              if (!ModelState.IsValid)             {                 return View();             }             else             {                 try                 {                     _entities.AddToContactSet(contactToCreate);                     _entities.SaveChanges();                     return RedirectToAction("Index");                 }                 catch                 {                     return View();                 }             }         }

  • FirstName属性的长度必须大于0(并且不计算空格)
  • LastName属性长度必须大于0(并且不计算空格)。
  • 如果Phone属性有值(且长度大于0),则Phone属性必须匹配正则表达式。
  • 如果Email属性有值(且长度大于0),则Email属性必须匹配正则表达式。

如果违反了其中某条验证规则,则一条错误消息将通过AddModelError()方法添加至ModelState中,你只需指定需验证的属性以及违反相应验证规则时显示的错误提示信息。这条信息将会显示在view中调用Html.ValidationSummary()和Html.ValidationMessage()方法的地方。

验证规则执行后就可以使用ModelState的IsValid属性了。该属性根据属性是否遵循ModelState中的规则返回一个布尔值。如果未通过验证,Create表单中将显示错误信息。

我是从​http://regexlib.com/">​http://regexlib.com/​​的“正则表达式仓库”中找到验证电话号码和电子邮件地址的正则表达式的,希望它能帮到你。

为Edit Action添加验证规则

Edit() action用来更新一个联系人信息。它需要执行与Create() action中基本相似的验证规则。我们在这里重构Contact controller使得Create()及Edit() action可以复用验证规则。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Ajax; using ContactManager.Models;  namespace ContactManager.Controllers {     public class ContactController : Controller     {          private ContactManagerEntities _entities = new ContactManagerEntities();          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.");         }          //         // GET: /Home/         public ActionResult Index()         {             return View(_entities.ContactSet.ToList());         }          //         // GET: /Home/Details/5          public ActionResult Details(int id)         {             return View();         }          //         // GET: /Home/Create          public ActionResult Create()         {             return View();         }          //         // POST: /Home/Create          [AcceptVerbs(HttpVerbs.Post)]         public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)         {             //Validation logic              ValidateContact(contactToCreate);              if (!ModelState.IsValid)             {                 return View();             }             else             {                 try                 {                     _entities.AddToContactSet(contactToCreate);                     _entities.SaveChanges();                     return RedirectToAction("Index");                 }                 catch                 {                     return View();                 }             }         }          //         // GET: /Home/Edit/5          public ActionResult Edit(int id)         {             var contractToEdit = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault();             return View(contractToEdit);         }          //         // POST: /Home/Edit/5          [AcceptVerbs(HttpVerbs.Post)]         public ActionResult Edit(Contact contactToEdit)         {              ValidateContact(contactToEdit);             if (!ModelState.IsValid)                 return View();             try             {                 var originalContact = _entities.ContactSet.Where(c => c.Id == contactToEdit.Id).FirstOrDefault();                 _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);                 _entities.SaveChanges();                 return RedirectToAction("Index");             }             catch             {                 return View();             }         }          //         // GET: /Home/Delete/5         public ActionResult Delete(int id)         {             var contactToDelete = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault();             return View(contactToDelete);         }          //         // POST: /Home/Delete/5         [AcceptVerbs(HttpVerbs.Post)]         public ActionResult Delete(Contact contactToDelete)         {             try             {                 var originalContact = _entities.ContactSet.Where(c => c.Id == contactToDelete.Id).FirstOrDefault();                 _entities.DeleteObject(originalContact);                 _entities.SaveChanges();                 return RedirectToAction("Index");             }             catch             {                 return View();             }         }     } }

总结

在本次迭代中,我们为Contact Manager应用添加了基本的表单验证。我们的验证逻辑会阻止用户在新增联系人、编辑联系人的情景下将未填写FirstName或LastName属性的表单进行提交。并且用户需要填写有效的电话号码和电子邮箱地址

本文中我们使用了最简单的方式为Contact Manager应用添加验证逻辑,然而将验证规则与controller耦合将会对应用程序的维护产生深远的负面影响。我们的应用将越来越难维护和修改。

我们会在下一次迭代中重构我们的验证逻辑和数据库存储逻辑并将其与controller解耦。得益于适当设计模式的帮助,应用中耦合将更加松散且程序更易维护。