摘要: 在這篇文章中,我將在一個例子中實際地展示MVC。 場景 假設一個朋友決定舉辦一個新年晚會,她邀請我創建一個用來邀請朋友參加晚會的WEB程式。她提出了四個註意的需求: 一個首頁展示這個晚會 一個表單用來提交申請 驗證表單內容,成功後顯示謝謝頁面 完成後給主人發送申請郵件 添加Model類Gue ...
摘要:
在這篇文章中,我將在一個例子中實際地展示MVC。
場景
假設一個朋友決定舉辦一個新年晚會,她邀請我創建一個用來邀請朋友參加晚會的WEB程式。她提出了四個註意的需求:
- 一個首頁展示這個晚會
- 一個表單用來提交申請
- 驗證表單內容,成功後顯示謝謝頁面
- 完成後給主人發送申請郵件
添加Model類GuestResponse
任何程式都應該是以數據為中心。因此,首先,在工程內添加Domain Model。
在工程根部創建Model文件夾。
在Model文件夾內創建GuestResponse.cs代碼文件。
修改GustResponse代碼。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace PartyInvite.Models 7 { 8 public class GuestResponse 9 { 10 public string Name { get; set; } 11 public string Email { get; set; } 12 public string Phone { get; set; } 13 public bool? WillAttend { get; set; } 14 } 15 }
修改Index視圖。
我將打算將Home/Index作為首頁,修改Index視圖。
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index</title> 11 </head> 12 <body> 13 <div> 14 @ViewBag.Greeting World (from the view) 15 <p> 16 We're going to have an exciting party.<br /> 17 (To do: sell it better. Add pictures or something.) 18 </p> 19 @Html.ActionLink("RSVP Now", "RsvpForm") 20 </div> 21 </body> 22 </html>
執行程式,在瀏覽器中得到運行結果。
在頁面底部出現了一個“RSVP Now鏈接”,這個鏈接是方法Html.ActionLink得到的。
將滑鼠移到該鏈接上,可以看到鏈接指向了/Home/RsvpForm鏈接。
為工程指定預設頁面
滑鼠右鍵工程PartyInvites,在彈出的菜單中選擇Properties。在Web選項卡中選擇“Specific page”,在輸入框中輸入Home/Index。
為RSVP添加Action和視圖
返回HomeController,添加Action。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 namespace PartyInvites.Controllers 8 { 9 public class HomeController : Controller 10 { 11 // GET: Home 12 public ViewResult Index() 13 { 14 int hour = System.DateTime.Now.Hour; 15 ViewBag.Greeting = hour < 12 ? "Good Moring" : "Good Afternoon"; 16 return View(); 17 } 18 19 public ViewResult RsvpForm() 20 { 21 return View(); 22 } 23 } 24 }
為RsvpForm這個Action添加視圖。
模板選擇“Empty”,Model class選擇剛創建的Domain Model類GuestResponse。點擊“Add”按鈕添加。
修改添加的視圖RsvpForm.cshtml。
1 @model PartyInvite.Models.GuestResponse 2 3 @{ 4 Layout = null; 5 } 6 7 <!DOCTYPE html> 8 9 <html> 10 <head> 11 <meta name="viewport" content="width=device-width" /> 12 <title>RsvpForm</title> 13 </head> 14 <body> 15 @using (Html.BeginForm()) 16 { 17 <p>Your name: @Html.TextBoxFor(x => x.Name) </p> 18 <p>Your email: @Html.TextBoxFor(x => x.Email)</p> 19 <p>Your phone: @Html.TextBoxFor(x => x.Phone)</p> 20 <p> 21 Will you attend? 22 @Html.DropDownListFor(x => x.WillAttend, new[] { 23 new SelectListItem() {Text = "Yes, I'll be there", 24 Value = bool.TrueString}, 25 new SelectListItem() {Text = "No, I can't come", 26 Value = bool.FalseString} 27 }, "Choose an option") 28 </p> 29 <input type="submit" value="Submit RSVP" /> 30 } 31 </body> 32 </html>
一些解釋:
@model PartyInvite.Models.GuestResponse:此視圖以GuestResponse類作為它的Model。
@using (Html.BeginForm()) {}: 調用Html幫助類,創建一個表單,大括弧裡面的內容是表單內容。
@Html.TextBoxFor、@Html.DropDownListFor:調用Html幫助類,傳入lamda表達式,為表單創建輸入HTML元素。
執行程式,在瀏覽器中得到的Index頁面中點擊“RSVP Now”鏈接,得到下麵的頁面。
處理表單
我還沒有告訴MVC當表單提交到伺服器端的時候我響應做什麼。按照目前的情況,點擊提交按鈕只是清空你剛纔填寫的信息。這是因為表單提交到Home控制器的RsvpForm行為方法,這個方法只是告訴MVC再次呈現這個視圖。
獲取和處理提交的表單數據,我將要使用一個聰明的方法。我將添加第二個RsvpForm行為方法來做下麵的事情:
- 一個方法響應HTTP GET請求。GET請求是每當用戶點擊一個鏈接時瀏覽器發出的。當用戶第一次訪問/Home/RsvpForm的時候展示初始空白表單調用這個版本的方法。
- 一個響應HTTP提交請求的方法:預設的,使用Html.BeginForm()方法呈現的表單由瀏覽器提交一個POST請求。這個版本的行為方法負責獲取提交數據以及決定拿到數據做什麼事情。
在分開的C#方法中處理GET和POST請求讓我的控制器打開變得簡潔,因為兩個方法有不同的責任。兩個行為方法都由相同的URL激活,但是MVC根據處理的是一個GET還是POST請求確保合適的方法被調用。下麵是修改後的HomeController類。
1 using PartyInvite.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace PartyInvites.Controllers 9 { 10 public class HomeController : Controller 11 { 12 public ViewResult Index() 13 { 14 int hour = System.DateTime.Now.Hour; 15 ViewBag.Greeting = hour < 12 ? "Good Moring" : "Good Afternoon"; 16 return View(); 17 } 18 19 [HttpGet] 20 public ViewResult RsvpForm() 21 { 22 return View(); 23 } 24 25 [HttpPost] 26 public ViewResult RsvpForm(GuestResponse guestResponse) 27 { 28 // TODO: Email response to the party organizer 29 return View("Thanks", guestResponse); 30 } 31 } 32 }
我在現在的RsvpForm行為方法上添加了HttpGet特性。這告訴MVC這個方法應該只用來處理GET請求。然後添加一個重載版本的RsvpForm,傳入一個GustResponse參數,用HttpPost特性修飾。這個特性告訴MVC這個心的方法處理Post請求。
呈現其他視圖
第二個重載的RsvpForm行為方法也展示了怎樣告訴MVC為請求的響應呈現一個具體的視圖,而不是預設的視圖,這是相關的語句:
return View("Thanks", guestResponse);
這調用View方法告訴MVC尋找並呈現一個名字叫“Thanks”的視圖,並傳遞GuestResponse對象給這個視圖。
在Views/Home文件夾中創建Thanks視圖。
修改視圖代碼:
@model PartyInvite.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Thanks</title> </head> <body> <div> <h1>Thank you, @Model.Name!</h1> <div> @if (Model.WillAttend == true) { <p>It's great that you're coming. The drinks are already in the fridge!</p> } else { @:Sorry to hear that you can't make it, but thanks for letting us know. } </div> </div> </body> </html>
基於我傳給視圖方法RsvpForm的參數GuestResponse對象屬性值,這個Thanks視圖使用Razor展示內容。Razor表達式@Model引用我寫死的Domain Model類型,得到對象的屬性值Model.Name。
執行程式,在RsvpForm表單中填寫數據,點擊“Submit” 按鈕,跳轉到Thanks頁面。
添加驗證
我現在要給我的應用程式添加驗證。沒有驗證,用戶可能輸入無意義的數據或者甚至提交空的表單。在MVC應用程式里,把Domain Model(域模型)應用到驗證,而不是用戶介面。這意味著我可以在一個地方定義驗證準則,在模塊類被使用的應用程式里任何地方都生效。ASP.NET MVC支持定義System.ComponentModel.DataAnnotations名稱空間的特性聲明式驗證規則,意味著驗證約束使用標準的C#特性來表達。
修改GuestResponse類。
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.DataAnnotations; 4 using System.Linq; 5 using System.Web; 6 7 namespace PartyInvite.Models 8 { 9 public class GuestResponse 10 { 11 [Required(ErrorMessage = "Please enter your name")] 12 public string Name { get; set; } 13 [Required(ErrorMessage = "Please enter your email address")] 14 [RegularExpression(".+\\@.+\\..+", ErrorMessage = "Please enter a valid email address")] 15 public string Email { get; set; } 16 [Required(ErrorMessage = "Please enter your phone number")] 17 public string Phone { get; set; } 18 [Required(ErrorMessage = "Please specify whether you'll attend")] 19 public bool? WillAttend { get; set; } 20 } 21 }
驗證代碼用粗體字顯示。MVC在模型-綁定過程中自動探測特性並使用他們驗證數據。
在Controller類里,我使用ModelState.IsValid屬性檢驗是否有驗證問題。下麵是修改後的Controller類RsvpForm方法。
1 [HttpPost] 2 public ViewResult RsvpForm(GuestResponse guestResponse) 3 { 4 if (ModelState.IsValid) 5 { 6 return View("Thanks", guestResponse); 7 } 8 else 9 { 10 return View(); 11 } 12 }
如果沒有驗證錯誤,我告訴MVC呈現Thanks視圖,像我之前那樣。如果有驗證錯誤,我調用不含參數的View方法重新呈現這個表單。
只是顯示這個表單而不顯示錯誤信息是沒有什麼幫助的-我還需要為用戶提供一些錯誤提示信息以及為什麼我不能獲得表單的提交數據。我在RsvpForm視圖裡使用Html.ValidationSummary幫助方法來達到這個目的。下麵是修改後的RsvpForm視圖。
如果沒有錯誤,Html.ValidationSummary方法在表單內創建一個隱藏的列表項作為占位符。MVC使得占位符變得可見並將定義在驗證屬性的錯誤消息顯示在上面。
提交空白的RsvpForm表單,得到下麵的運行結果。
將不會給用戶顯示Thanks視圖,直到所有的運用在GuestResponse類的驗證約束都被滿足。註意輸入到在視圖被呈現的時候,表單的數據被保留了並和驗證摘要信息一起重新顯示。
高亮顯示非法輸入欄位
HTML幫助方法創建文本框、下拉框和其他的元素,這些元素有現成的可以用於模型綁定的特性。相同的保留用戶輸入到表單的數據機制同樣被用來高亮顯示驗證失敗的欄位。
當一個模型類屬性驗證失敗了,HTML幫助方法將產生稍微不同的HTML。
例如,下麵是調用Html.TextBoxFor(x=>x.Name)方法後,驗證沒有錯誤的文本框:
<input data-val="true" data-val-required="Please enter your name" id="Name" name="Name" type="text" value="" />
下麵是調用相同方法後,用戶沒有輸入值(這是一個驗證錯誤,因為我在Name屬性運用了Request特性)的文本框:
<input class="input-validation-error" data-val="true" data-val-required="Please enter your name" id="Name" name="Name" type="text" value="" />
幫助方法添加了一個名稱為input-validation-error的類到元素上。我可以利用這個特性,創建一個包含這個css類樣式的樣式表。
在根目錄下添加Content文件夾,在Content文件夾內添加樣式表文件Styles.css。
1 .field-validation-error {color: #f00;} 2 .field-validation-valid { display: none;} 3 .input-validation-error { border: 1px solid #f00; background-color:#fee; } 4 .validation-summary-errors { font-weight: bold; color: #f00;} 5 .validation-summary-valid { display: none;}
修改RsvpForm視圖,添加樣式表引用。
1 @model PartyInvite.Models.GuestResponse 2 3 @{ 4 Layout = null; 5 } 6 7 <!DOCTYPE html> 8 9 <html> 10 <head> 11 <meta name="viewport" content="width=device-width" /> 12 <link rel="stylesheet" type="text/css" href="~/Content/Styles.css" /> 13 <title>RsvpForm</title> 14 </head> 15 <body> 16 @using (Html.BeginForm()) 17 { 18 @Html.ValidationSummary() 19 <p>Your name: @Html.TextBoxFor(x => x.Name) </p> 20 <p>Your email: @Html.TextBoxFor(x => x.Email)</p> 21 <p>Your phone: @Html.TextBoxFor(x => x.Phone)</p> 22 <p> 23 Will you attend? 24 @Html.DropDownListFor(x => x.WillAttend, new[] { 25 new SelectListItem() { Text = "Yes, I'll be there", Value = bool.TrueString }, 26 new SelectListItem() { Text = "No, I can't come", Value = bool.FalseString } 27 }, "Choose an option") 28 </p> 29 <input type="submit" value="Submit RSVP" /> 30 } 31 </body> 32 </html>
當RsvpForm表單輸入錯誤的時候,頁面將顯示如下所示、
給視圖添加樣式
基本的應用程式功能已經做好了-除了發送郵件,但是整體的外觀很簡陋。
這裡我使用Bootstrap庫,這是早先是Twitter公司開發的現在用得很廣的一個CSS庫。
使用NutGet導入Bootstrap庫。Bootstrap庫的CSS文件將導入到Content文件夾下,JavaScript文件將導入到Scripts文件夾下。
修改Index視圖。
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <link href="~/Content/bootstrap.css" rel="stylesheet" /> 11 <link href="~/Content/bootstrap-theme.css" rel="stylesheet" /> 12 <title>Index</title> 13 <style> 14 .btn a { 15 color: white; 16 text-decoration: none; 17 } 18 19 body { 20 background-color: #F1F1F1; 21 } 22 </style> 23 </head> 24 <body> 25 <div class="text-center"> 26 <h2>We're going to have an exciting party!</h2> 27 <h3>And you are invited</h3> 28 <div class="btn btn-success"> 29 @Html.ActionLink("RSVP Now", "RsvpForm") 30 </div> 31 </div> 32 </body> 33 </html>
修改RsvpForm視圖。
1 @model PartyInvite.Models.GuestResponse 2 3 @{ 4 Layout = null; 5 } 6 7 <!DOCTYPE html> 8 9 <html> 10 <head> 11 <meta name="viewport" content="width=device-width" /> 12 <link href="~/Content/bootstrap.css" rel="stylesheet" /> 13 <link href="~/Content/bootstrap-theme.css" rel="stylesheet" /> 14 <title>RsvpForm</title> 15 </head> 16 <body> 17 <div class="panel panel-success"> 18 <div class="panel-heading text-center"><h4>RSVP</h4></div> 19 <div class="panel-body"> 20 @using (Html.BeginForm()) 21 { 22 @Html.ValidationSummary() 23 <div class="form-group"> 24 <label>Your name:</label>@Html.TextBoxFor(x => x.Name) 25 </div> 26 <div class="form-group"> 27 <label>Your Email:</label> @Html.TextBoxFor(x => x.Email) 28 </div> 29 <div class="form-group"> 30 <label>Your Phone:</label> @Html.TextBoxFor(x => x.Phone) 31 </div> 32 <div class="form-group"> 33 <label> 34 Will you attend? 35 </label> 36 @Html.DropDownListFor(x => x.WillAttend, 37 new[] { 38 new SelectListItem() { Text="Yes, I will be there" , Value = bool.TrueString }, 39 new SelectListItem() { Text = "Yes, I will be there", Value = bool.TrueString } 40 }, "Choose an option") 41 </div> 42 <div class="text-center"> 43 <input class="btn btn-success" type="submit" 44 value="Submit RSVP" /> 45 </div> 46 } 47 </div> 48 </div> 49 </body> 50 </html>
修改Thanks視圖。
1 @model PartyInvite.Models.GuestResponse 2 3 @{ 4 Layout = null; 5 } 6 7 <!DOCTYPE html> 8 9 <html> 10 <head> 11 <meta name="viewport" content="width=device-width" /> 12 <link href="~/Content/bootstrap.css" rel="stylesheet" /> 13 <link href="~/Content/bootstrap-theme.css" rel="stylesheet" /> 14 <title>Thanks</title> 15 <style> 16 body { 17 background-color: #F1F1F1; 18 } 19 </style> 20 </head> 21 <body> 22 <div class="text-center"> 23 <h1>Thank you, @Model.Name!</h1> 24 <div class="lead"> 25 @if (Model.WillAttend == true) 26 { 27 <p>It's great that you're coming. The drinks are already in the fridge!</p> 28 } 29 else { 30 @:Sorry to hear that you can't make it, but thanks for letting us know. 31 } 32 </div> 33 </div> 34 </body> 35 </html>
運行