迭代 3 – 添加表单验证 (VB)
在第三次迭代中,我们添加了基本表单验证。 我们会阻止用户在未完成所需表单字段的情况下提交表单。 我们还验证电子邮件地址和电话号码。
生成联系人管理 ASP.NET MVC 应用程序 (VB)
在此系列教程中,我们将从头到尾构建整个联系人管理应用程序。 通过 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="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of ContactManager.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>
<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>
<% End Using %>
</asp:Content>
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.vb (使用验证) 创建
<AcceptVerbs(HttpVerbs.Post)> _
Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
' Validation logic
If contactToCreate.FirstName.Trim().Length = 0 Then
ModelState.AddModelError("FirstName", "First name is required.")
End If
If contactToCreate.LastName.Trim().Length = 0 Then
ModelState.AddModelError("LastName", "Last name is required.")
End If
If (contactToCreate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToCreate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.")
End If
If (contactToCreate.Email.Length > 0 AndAlso Not Regex.IsMatch(contactToCreate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.")
End If
If Not ModelState.IsValid Then
Return View()
End If
' Database logic
Try
_entities.AddToContactSet(contactToCreate)
_entities.SaveChanges()
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
validate 节强制实施四个不同的验证规则:
- 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.vb
Public Class ContactController
Inherits System.Web.Mvc.Controller
Private _entities As New ContactManagerDBEntities()
Protected Sub ValidateContact(contactToValidate As Contact)
If contactToValidate.FirstName.Trim().Length = 0 Then
ModelState.AddModelError("FirstName", "First name is required.")
End If
If contactToValidate.LastName.Trim().Length = 0 Then
ModelState.AddModelError("LastName", "Last name is required.")
End If
If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
ModelState.AddModelError("Phone", "Invalid phone number.")
End If
If (contactToValidate.Email.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
ModelState.AddModelError("Email", "Invalid email address.")
End If
End Sub
'
' GET: /Contact
Function Index() As ActionResult
Return View(_entities.ContactSet.ToList())
End Function
'
' GET: /Contact/Create
Function Create() As ActionResult
Return View()
End Function
'
' POST: /Contact/Create
<AcceptVerbs(HttpVerbs.Post)> _
Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
' Validation logic
ValidateContact(contactToCreate)
If Not ModelState.IsValid Then
Return View()
End If
' Database logic
Try
_entities.AddToContactSet(contactToCreate)
_entities.SaveChanges()
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
'
' GET: /Contact/Edit/5
Function Edit(ByVal id As Integer) As ActionResult
Dim contactToEdit = (from c in _entities.ContactSet _
where c.Id = id _
select c).FirstOrDefault()
Return View(contactToEdit)
End Function
'
' POST: /Contact/Edit/5
<AcceptVerbs(HttpVerbs.Post)> _
Function Edit(ByVal contactToEdit As Contact) As ActionResult
' Validation logic
ValidateContact(contactToEdit)
If Not ModelState.IsValid Then
Return View()
End If
' Database logic
Try
Dim 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()
End Try
End Function
'
' GET: /Contact/Delete/5
Function Delete(ByVal id As Integer) As ActionResult
Dim contactToDelete = (from c in _entities.ContactSet _
where c.Id = id _
select c).FirstOrDefault()
Return View(contactToDelete)
End Function
'
' POST: /Contact/Delete/5
<AcceptVerbs(HttpVerbs.Post)> _
Function Delete(ByVal contactToDelete As Contact) As ActionResult
Try
Dim 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()
End Try
End Function
End Class
总结
在此迭代中,我们向 Contact Manager 应用程序添加了基本表单验证。 我们的验证逻辑阻止用户提交新联系人或编辑现有联系人,而不提供 FirstName 和 LastName 属性的值。 此外,用户必须提供有效的电话号码和电子邮件地址。
在此迭代中,我们以最简单的方式将验证逻辑添加到 Contact Manager 应用程序。 但是,从长远来看,将验证逻辑混合到控制器逻辑中会给我们造成问题。 随着时间的推移,我们的应用程序将更难维护和修改。
在下一次迭代中,我们将从控制器中重构验证逻辑和数据库访问逻辑。 我们将利用一些软件设计原则来创建更松散耦合且更易于维护的应用程序。