共用方式為


反覆項目 #3 – 新增表單驗證 (C#)

Microsoft 提供

下載程式碼

在第三個反覆項目中,我們新增基本表單驗證。 我們要防止人員提交未完成表單必填欄位的表單。 我們也驗證電子郵件地址和電話號碼。

建置連絡人管理 ASP.NET MVC 應用程式 (C#)

在此系列教學課程中,我們會從頭到尾建置整個連絡人管理應用程式。 Contact Manager 應用程式可讓您儲存連絡人資訊 (姓名、電話號碼和電子郵件地址) 以取得人員名單。

我們會透過多個反覆項目建置應用程式。 每次反覆運算時,我們會逐步改善應用程式。 這種多次反覆運算方法的目標是,讓您能夠瞭解每次變更的原因。

  • 反覆項目 #1 – 建立應用程式 在第一個反覆項目中,我們以最簡單的方式建立 Contact Manager。 我們新增對基本資料庫作業的支援:建立、讀取、更新和刪除 (CRUD)。

  • 反覆項目 #2 – 美化應用程式外觀。 在這個反覆項目中,我們修改預設 ASP.NET MVC 檢視主版頁面和串聯樣式表,以改善應用程式的外觀。

  • 反覆項目 #3 – 新增表單驗證。 在第三個反覆項目中,我們新增基本表單驗證。 我們要防止人員提交未完成表單必填欄位的表單。 我們也驗證電子郵件地址和電話號碼。

  • 反覆項目 #4 – 讓應用程式鬆散耦合。 在這個第四個反覆項目中,我們利用好幾種軟體設計模式,更輕鬆地維護和修改 Contact Manager 應用程式。 例如,我們將應用程式重構為使用存放庫模式和相依性插入模式。

  • 反覆項目 #5 – 建立單元測試。 在第五個反覆項目中,我們會藉由新增單元測試,更容易維護和修改應用程式。 我們會模擬資料模型類別,並為控制器和驗證邏輯建置單元測試。

  • 反覆項目 #6 – 使用測試導向的開發。 在這第六個反覆項目中,我們會先撰寫單元測試,再針對單元測試撰寫程式碼,藉此將新功能新增至應用程式。 在此反覆項目中,我們會新增連絡人群組。

  • 反覆項目 #7 – 新增 Ajax 功能性。 在第七個反覆項目中,我們會藉由新增對 Ajax 的支援來改善應用程式的回應性和效能。

此反覆項目

在這個 Contact Manager 應用程式的第二個反覆項目中,我們會新增基本表單驗證。 我們避免使用者在未輸入必填表單欄位值的情況下提交連絡人。 我們也驗證電話號碼和電子郵件地址 (請參閱圖 1)。

[New Project] \(新增專案\) 對話方塊

圖 01:含有驗證的表單 (按一下以檢視完整大小的圖片)

在此反覆項目中,我們會將驗證邏輯直接新增至控制器動作。 一般來說,這不是向 ASP.NET MVC 應用程式新增驗證的建議方法。 更好的方法是將應用程式的驗證邏輯放置在單獨的服務層中。 在下一個反覆項目中,我們會重構 Contact Manager 應用程式,讓應用程式更容易維護。

在此反覆項目中,為了保持簡單,我們會手動撰寫所有驗證程式碼。 我們可以利用驗證架構,而不是自己編寫驗證程式碼。 例如,您可以使用 Microsoft Enterprise Library Validation Application Block (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 應用程式時,會自動在 [內容] 資料夾中建立名為 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() 協助程式所轉譯的文字方塊 (input) 樣式。 validation-summary-errors 類別可用來設定 Html.ValidationSummary() 協助程式所轉譯之未排序清單的樣式。

注意

您可以修改本節所述的樣式表類別,以自訂驗證錯誤訊息的外觀。

將驗證邏輯新增至建立動作

現在,[建立] 檢視永遠不會顯示驗證錯誤訊息,因為我們尚未撰寫邏輯來產生任何訊息。 若要顯示驗證錯誤訊息,您必須將錯誤訊息新增至 ModelState。

注意

UpdateModel() 方法會在將表單欄位的值指派給屬性時,自動將錯誤訊息新增至 ModelState。 例如,如果您嘗試將字串「apple」指派給接受 DateTime 值的 BirthDate 屬性,則 UpdateModel() 方法會將錯誤新增至 ModelState。

清單 2 中修改過的 Create() 方法包含新區段,在新的連絡人插入資料庫之前,會先驗證 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 屬性必須符合規則運算式。

發生驗證規則違規時,會在 AddModelError() 方法的協助下,將錯誤訊息新增至 ModelState。 當您將訊息新增至 ModelState 時,您會提供屬性的名稱和驗證錯誤訊息的文字。 這個錯誤訊息會顯示在 Html.ValidationSummary() 和 Html.ValidationMessage() 協助程式方法的檢視中。

執行驗證規則之後,會檢查 ModelState 的 IsValid 屬性。 當任何驗證錯誤訊息已新增至 ModelState 時,IsValid 屬性會傳回 false。 如果驗證失敗,[建立] 表單會以錯誤訊息重新顯示。

注意

我從 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();
            }
        }

    }
}

摘要

在此反覆項目中,我們已將基本表單驗證新增至 Contact Manager 應用程式。 我們的驗證邏輯可防止使用者在未提供 FirstName 和 LastName 屬性值的情況下,提交新連絡人或編輯現有連絡人。 此外,使用者必須提供有效的電話號碼和電子郵件地址。

在此反覆項目中,我們會以最簡單的方式,將驗證邏輯新增至 Contact Manager 應用程式。 然而,從長遠來看,將驗證邏輯混合到控制器邏輯中會為我們帶來問題。 隨著時間推移,我們的應用程式將更加難以維護和修改。

在下一個反覆項目中,我們會將驗證邏輯和資料庫存取邏輯重構為控制器。 我們將利用數個軟體設計原則,讓我們能夠建立更鬆散耦合且更容易維護的應用程式。