整個web應用平台的關注點在於構建並顯示動態輸出內容。在MVC裡,控制器負責構建一些數據並將其傳給視圖。視圖負責渲染成HTML。 從控制器向視圖傳遞數據的一種方式是使用ViewBag 對象,它是一個控制器基類的成員。ViewBag是一個動態對象,你可以給他賦值任意屬性給視圖來渲染用。代碼2-5 演示了如何在HomeController裡傳遞簡單對象。 Listing 2-5. 設置視圖數據
using System;
using Microsoft.AspNetCore.Mvc;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
}
}
我向ViewBag.Greeting屬性賦值,以給視圖提供數據。Greeting屬性在賦值之前是不存在的,這允許我以任意流暢的方式從控制器向視圖傳遞數據而不必在賦值之前定義類。我在視圖中引用了ViewBag.Greeting屬性以獲得他的值。如同代碼2-6,這是修改後的MyView.cshtml。
Listing 2-6. 在視圖裡獲取傳遞過來的值
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@ViewBag.Greeting World (from the view)
</div>
</body>
</html>
上面代碼增加的部分是Razor表達式,它在MVC 使用視圖生成相應的時候求值。當我在控制器內調用View方法的時候,MVC找到MyView.cshtml文件並請求Razor 視圖引擎解析文件的內容。Razor 會查找象上面代碼中的表達式。在本例中,處理表達式的意思是將ViewBag.Greeting屬性插入到視圖中。
Greeting這個屬性名字沒有什麼特殊的東西,你可以使用任何其他的名字,並且一樣好用。只要你在控制器中的名字與視圖中的名字相同即可。你可以使用多個屬性來傳遞多個數據。然後你運行一下看一下效果,如圖2-13。
圖2-13 一個MVC的動態響應
在本章的剩下的部分,我將通過構建一個簡單的數據錄入應用來探索更多的基本MVC特征。這一節,我將會加快點速度。我的目標是用action來演示MVC,所以我將略過去一些講解有些東西的內部原理。但是不要擔心,我將會在以後的章節中討論那些內容的。
想像一下,一個朋友決定了要舉行一個新年晚會,他請我建立一個web 應用來跟蹤它通過電子邀請函邀請的朋友。她需要以下四個關鍵功能:
在接下來的段落裡,我將在前面MVC工程的基礎上逐漸增加內容,增加那些功能。第一步,我將馬上就實現列表中的第一項,因為前面已經做了一些工作,只需要向現有的視圖增加一些HTML來給出晚會的信息即可。代碼2-7 顯示了我向Views/Home/MyView.cshtml文件中增加的內容。
Listing 2-7. 顯示晚會明細
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@ViewBag.Greeting World (from the view)
<p>We're going to have an exciting party.<br />
(To do: sell it better. Add pictures or something.)
</p>
</div>
</body>
</html>
我繼續。 如果你運行,你會看見晚會的信息,(額,還是一些占位符,但是你已經明白了吧)如圖2-14.
圖 2-14 向視圖裡增加HTML
在MVC裡,M 代表模型, 他是應用程序裡最重要的部分。模型是真實世界對象的代表,處理,定義領域的規則。模型經常被稱為是領域模型,包含C# 對象(領域對象)構成應用程序的世界和操縱他們的方法。視圖和控制器會將領域用一致的方式暴露給客戶端,並且,一個設計良好的MVC應用程序應該從一個設計良好的模型開始。然後控制器和視圖才加入。
在PartyInvites工程裡,我不需要復雜的模型,因為這是一個非常簡單的應用,我只需要建立一個領域類,然後我將會調用GuestResponse.這個對象將負責保存,驗證和確認一個邀請函。
MVC的約定一般把模型放在Models文件夾裡,要建立這個文件夾,右擊PartyInvites工程,從菜單中選擇Add->New Folder ,然後設置名字為Models。
注意: 當程序運行時,你不能設置一個文件夾的名字。你可以從Debug菜單裡選擇Stop Debugging,右擊你已經加入的NewFolder項,然後從彈出菜單中選擇Rename,然後改成Models。
要建立一個類文件,右擊Models文件夾,然後在彈出菜單裡選擇Add->Class。 設置新的類名字為GuestResponse.cs ,然後單擊Add按鈕,編輯新類的內容為代碼2-8.
Listing 2-8 GuestResponse 領域類定義
namespace PartyInvites.Models {
public class GuestResponse {
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool? WillAttend { get; set; }
}
}
提示: 你可能已經注意到了 WillAttend 屬性是一個可空的bool型,意思是它可以是true,false,或null。我將解釋她的基本原理在本章的"增加校驗"節。
我的應用程序的一個目標是包含一個邀請函窗體,也就是我將要定義一個行動(action)方法可以為它接收請求。一個單獨的控制器類可以定義多個行動方法,默認約定是將相關的行動放到同一個控制器內。代碼2-9展現了Home 控制器中新增加的行動方法。
Listing 2-9. Adding an Action Method in the HomeController.cs File
using System;
using Microsoft.AspNetCore.Mvc;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
public ViewResult RsvpForm() {
return View();
}
}
}
RsvpForm 行動方法調用View方法,不帶參數,這將會告訴MVC取渲染連接於該行動方法的默認的視圖,與行動方法的名字相同,在這裡是RsvpForm.cshtml。 右擊Views->Home 文件夾並在彈出菜單中選擇Add->New Item。從ASP.NET 分類裡選擇MVC View Page模板,設置新的名字為RsvpForm.cshtml,並點擊Add 按鈕來創建文件。修改該文件的內容,讓他變成代碼 2-10那樣。
Listing 2-10. 設置視圖文件
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<div>
This is the RsvpForm.cshtml View
</div>
</body>
</html>
上面內容大部分為HTML,中間夾雜有@model Razor表達式,用來創建一個強類型的視圖。強類型的視圖用來渲染特定類型的模型,如果我指定一個類型(本例中,GuestResponse類),MVC可以創建一些有用的快捷方式並使它更容易。一會兒我將利用強類型的特征。 要測試新的行動方法和它的視圖,啟動應用程序,並使用浏覽器浏覽/Home/RsvpForm 。 MVC將使用命名約定來重定向請求到Home控制器中的RsvpForm行動方法。這個行動方法告訴MVC去渲染默認的視圖,這裡又使用了另一個命名規范,渲染RsvpForm.cshtml。圖2-15展示了結果。
圖2-15 渲染第二個視圖
我想要從MyView視圖內建立一個連接,以便我的客人能夠看見RsvpForm視圖而不必知道URL。如代碼2-11。
Listing 2-11. 在MyView.cshtml裡增加一個連接
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@ViewBag.Greeting World (from the view)
<p>We're going to have an exciting party.<br />
(To do: sell it better. Add pictures or something.)
</p>
<a asp-action="RsvpForm">RSVP Now</a>
</div>
</body>
</html>
代碼中增加的部分是一個具有asp-action屬性的<a>
標記。這是一個標記幫助器(tag helper)屬性,他是一個給Razor的指令,在視圖渲染的時候執行。這裡的asp-action屬性是用來給<a>
標記增加一個href屬性,包含指向一個行動方法的URL。我將會在第24,25和26章結束適合使用標記幫助器。但是這裡是一個<a>
標記的簡單的標記幫助器,他告訴Razor 插入定義在與本視圖相同的控制器中定義的一個行動方法的URL。程序運行的時候你會看到這個鏈接。如圖2-16所示。
圖 2-16 在行動方法之間加鏈接
啟動應用程序並將鼠標放在RSVP Now連接的上面,你會看到連接指向的是下面的URL(端口可能會有不同): http://localhost:57628/Home/RsvpForm
這裡有一個重要的原則,即你應該使用MVC生成URL的功能,而不是在你的視圖裡硬編碼。當標記幫助器給<a
標記建立href屬性時,他會檢查當前應用程序的配置並計算出URL是什麼樣子。這允許改變應用程序配置來支持不同的URL格式而無需更新視圖。我將會在第15章解釋其原理。
現在我已經建立了強類型的視圖,並且能夠在Index視圖中連接到它,我將在RsvpForm.cshtml文件裡增加一些內容,並使他們變成一個HTML表單,用來編輯GuestResponse對象,如代碼2-12。
Listing 2-12. 建立一個輸入表單視圖
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<form asp-action="RsvpForm" method="post">
<p>
<label asp-for="Name">Your name:</label>
<input asp-for="Name" />
</p>
<p>
<label asp-for="Email">Your email:</label>
<input asp-for="Email" />
</p>
<p>
<label asp-for="Phone">Your phone:</label>
<input asp-for="Phone" /></p>
<p>
<label>Will you attend?</label>
<select asp-for="WillAttend">
<option value="">Choose an option</option>
<option value="true">Yes, I'll be there</option>
<option value="false">No, I can't come</option>
</select>
</p>
<button type="submit">Submit RSVP</button>
</form>
</body>
</html>
我已經為GuestResponse模型類的每一個屬性定義了一個標簽和輸入元素。每一個元素都使用asp-for屬性連接一個模型的屬性。這裡的asp-for是另一個標簽幫助器,他能配置將元素連接到模型對象。下面是一個例子,由標記幫助器生成,發給浏覽器的HTML:
<p>
<label for="Name">Your name:</label>
<input type="text" id="Name" name="Name" value="">
</p>
這裡Label 上的asp-for 幫助器可以給for屬性設置值。input 元素上的asp-for 可以設置元素的id 和name。這些都不是什麼特殊的用途,但是你將會看到將元素連接到模型的屬性會帶來更多的好處。 馬上你就會看到更多的asp-action屬性應用到了form元素, 它使用了應用程序的URL路由配置來設置form的action屬性為一個指向到一個特殊的行動方法的URL,如下面這樣:
<form method="post" action="/Home/RsvpForm">
與我應用到元素的助手屬性一樣,這種方法的好處是更改應用程序使用的URL系統,標簽助手生成的內容將反映自動變化。 運行該應用程序並點擊RSVP Now 連接,可以看到窗體,如圖2-17。
圖 2-17 在行動方法之間加鏈接
我還沒有告訴MVC當表單被發送到服務器時我想做什麼。目前來看,點擊Submit RSVP按鈕只是清除您輸入到表單中的值。那是因為表單將數據發回Home控制器的RsvpForm行動方法後只告訴MVC再次渲染這個視圖,沒做別的事情。 要接收和處理提交的表單數據,我將使用核心控制器功能。我會加第二個RsvpForm動作方法來建立下面這些內容:
Listing 2-13. 增加一個行動方法來支持POST請求
using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
[HttpGet]
public ViewResult RsvpForm() {
return View();
}
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
// TODO: store repsonse from guest
return View();
}
}
}
我已經給已經存在的RsvpForm行動方法加上了一個HttpGet的特性(attribute),這回告訴MVC此方法僅能夠被Get請求調用。然後我給RsvpForm方法增加了一個重載的版本,他可以接受GuestResponse 對象。 對這個方法我應用了HttpPost特性。它會告訴MVC 新的方法將處理POST請求。在後面的段落裡我將會介紹這些代碼是如何工作的。我也引入了一個叫做PartyInvites.Models的命名空間。加入它是為了我能夠使用GuestResponse 模型類型。
第一個重載的RsvpForm行動方法渲染的視圖同前面的一樣--RsvpForm.cshtml--是用來生成圖2-17那樣的表單的。第二個重載的是個更有意思的事,但是考慮到響應於HTTP POST請求將調用行動方法,GuestResponse類型是一個C#類,兩者是如何連接的?
答案是模型綁定,一個有用的MVC功能,其中輸入數據被解析成HTTP請求中的鍵/值對,用於填充領域模型類型的屬性。 模型綁定是一種功能強大且可自定義的功能,可以消除磨合而直接處理HTTP請求,並讓您使用C#對象,而不是處理由浏覽器發送的單個數據值。作為參數傳遞給Action方法的GuestResponse對象是自動填充表單字段中的數據的。 在第26章我將會介紹更多關於模型綁定的細節,包括如何定制。
應用程序中另一個目標是提供一個摘要頁面,其中包含參加人的詳細信息,這需要跟蹤我收到的回復。 我將通過創建一個內存中的Collection對象來做到這一點。 這在實際應用中是沒有什麼用途的,因為應用程序停止或重新啟動的話,數據將丟失,但這種方法允許我將重點放在MVC上並創建一個可以輕松地重置為初始狀態的應用程序。
提示:第8章中,我演示了一個更實際的示例應用程序,將演示如何在MVC中永久地存儲和訪問數據。
我通過右鍵單擊Models文件夾並從彈出窗口中選擇Add->Class,將文件添加到項目中。 我將文件的名稱設置為Repository.cs,並使用它來定義一個類,如清單2-14所示。
Listing 2-14. Repository.cs 文件的內容
using System.Collections.Generic;
namespace PartyInvites.Models {
public static class Repository {
private static List<GuestResponse> responses = new List<GuestResponse>();
public static IEnumerable<GuestResponse> Responses {
get {
return responses;
}
}
public static void AddResponse(GuestResponse response) {
responses.Add(response);
}
}
}
Repository類及其成員是靜態的,這將使我很容易從應用程序中的不同位置存儲和檢索數據。 MVC提供了一種更為復雜的方法來定義常見的功能,稱為依賴注入,我在第18章中描述,但靜態類是一個簡單的應用程序入門的好方法。
現在我有一個存儲數據的地方,我可以更新接收HTTP POST請求的操作方法,如清單2-15所示。
Listing 2-15. 更新行動方法
using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
[HttpGet]
public ViewResult RsvpForm() {
return View();
}
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
Repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
}
}
}
處理請求中發送的表單數據的所有操作都是與傳遞給行動方法的GuestResponse對象一起使用 - 在這種情況下,將其作為參數傳遞給Repository.AddResponse方法,以便保存響應。
為什麼模型綁定不像Web Forms?
在第一章中,我解釋說傳統ASP.NET Web窗體的一個缺點是它隱藏了開發人員的HTTP和HTML的細節。 您可能會想知道用於從代碼2-15中的HTTP POST請求創建GuestResponse對象的MVC模型綁定是否會做同樣的事情。 他沒有這樣做。模型綁定使我免除無聊且容易出錯的任務,因為我們必須檢查HTTP請求並提取我需要的所有數據值,但是(這是重要的部分),如果我想手動處理請求,那也可以,因為MVC可以方便地訪問所有的請求數據。MVC沒有對開發人員隱藏任何東西,但是有一些有用的功能可以使HTTP和HTML更簡單。對於這些功能你可以用,也可以不用,隨便。
這似乎是一個微妙的區別,但是當您了解有關MVC的更多信息時,您將看到開發體驗與傳統Web窗體完全不同,並且您可以始終能夠了解到應用程序收到的請求是如何處理的。
在RsvpForm操作方法中對View方法的調用告訴MVC渲染一個名為Thanks的視圖,並將GuestResponse對象傳遞給視圖。 要創建視圖,請右鍵單擊解決方案資源管理器中的“視圖/主頁”文件夾,然後從彈出菜單中選擇“添加”->“新建項目”。 在ASP.NET類別中選擇MVC視圖頁面模板,將名稱設置為Thanks.cshtml,然後單擊添加按鈕。 Visual Studio將創建Views / Home / Thanks.cshtml文件並打開它進行編輯。 用代碼2-16替換文件的內容。
Listing 2-16. Thanks.cshtml 文件的內容
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Thanks</title>
</head>
<body>
<p>
<h1>Thank you, @Model.Name!</h1>
@if (Model.WillAttend == true) {
@:It's great that you're coming. The drinks are already in the fridge!
} else {
@:Sorry to hear that you can't make it, but thanks for letting us know.
}
</p>
<p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p>
</body>
</html>
Thanks.cshtml視圖使用Razor來顯示RsvpForm操作方法中傳遞給View方法的GuestResponse屬性的值。 Razor中,@model表達式指定強制鍵入視圖的領域模型類型。 要訪問域對象中的屬性的值,我使用Model.PropertyName。 例如,要獲取Name屬性的值,我調用Model.Name。 如果不了解Razor的語法,不要擔心,我在第5章更詳細地解釋它。 現在我已經創建了Thanks視圖,我已經有了一個基本的工作示例-使用MVC一個表單。 通過從Debug菜單中選擇Start Debugging來啟動Visual Studio中的應用程序,單擊“RSVP Now”鏈接,在表單中添加一些數據,然後單擊“Submit RSVP”按鈕, 你會看到如圖2-18所示的結果(盡管如果你的名字不是Joe)。
圖2-18 感謝視圖
在Thanks.cshtml視圖的結尾,我添加了一個元素來創建一個鏈接來顯示參加派對的人員列表。 我使用asp-action標簽幫助器屬性來創建一個目標名為ListResponses的動作方法的URL,像這樣:
... <p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p>
...
如果您將鼠標懸停在浏覽器顯示的鏈接上,您將看到它的URL是/Home/ListResponses。 這與Home控制器中的任何操作方法不對應,如果單擊鏈接,您將看到一個空頁面。 打開浏覽器的開發工具並查看服務器發送的響應會顯示服務器發回404 - 未找到錯誤(Chrome有一點奇怪的是它不會向用戶顯示錯誤消息,但是 我將在第14章解釋如何產生有意義的錯誤消息)。 我將通過創建Home控制器中URL定位的操作方法來解決問題,如清單2-17所示。
Listing 2-17. 在控制器裡增加一個行動方法
using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
using System.Linq;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
[HttpGet]
public ViewResult RsvpForm() {
return View();
}
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
Repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
}
public ViewResult ListResponses() {
return View(Repository.Responses.Where(r => r.WillAttend == true));
}
}
}
新的Action方法稱為ListResponses,它使用Repository調用View方法。 響應屬性作為參數。 這是向強類型視圖提供數據的一種操作方法。 使用LINQ過濾GuestResponse對象的集合,以便得到正確響應。 ListResponses行動方法沒有指定應該用於顯示GuestResponse對象的集合的視圖的名稱,這意味著將使用默認的命名約定,MVC將在Views/Home和Views/Shared文件夾中查找名為ListResponses.cshtml的視圖。要創建視圖,請右鍵單擊解決方案資源管理器中的“視圖/主頁”文件夾,然後從彈出菜單中選擇“添加”->“新建項目”。 在ASP.NET類別中選擇MVC視圖頁面模板,將名稱設置為ListResponses.cshtml,然後單擊添加按鈕。 編輯新視圖的內容如代碼2-18。
Listing 2-18. 顯示接收
@model IEnumerable<PartyInvites.Models.GuestResponse>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Responses</title>
</head>
<body>
<h2>Here is the list of people attending the party</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
@foreach (PartyInvites.Models.GuestResponse r in Model) {
<tr>
<td>@r.Name</td>
<td>@r.Email</td>
<td>@r.Phone</td>
</tr>
}
</tbody>
</table>
</body>
</html>
Razor視圖文件具有cshtml文件擴展名,因為它們是C#代碼和HTML元素的組合。 您可以在清單2-18中看到這一點,其中我使用foreach循環來處理使用View方法將action方法傳遞給視圖的每個GuestResponse對象。與正常的C#foreach循環不同,Razor foreach循環的主體包含添加到將被發送回浏覽器的響應中的HTML元素。在此視圖中,每個GuestResponse對象都生成一個tr元素,其中包含用對象屬性的值填充的td元素。 要查看工作中的列表,請通過從開始菜單中選擇啟動調試來運行應用程序,提交一些表單數據,然後單擊鏈接以查看響應列表。您將看到從應用程序啟動後輸入的數據摘要,如圖2-19所示。該視圖呈現數據的方式不太美觀,但還可以了,本章稍後將介紹應用程序的樣式。
圖2-18 顯示參加人員列表
我現在可以向我的應用程序添加數據驗證。 沒有驗證,用戶可以瞎輸入數據,甚至提交一個空的表單。 在MVC應用程序中,通常將驗證應用於領域模型,而不是在用戶界面中。 您可以在一個位置定義驗證,但是它將在使用模型類的應用程序中的任何位置生效。 MVC支持使用system.ComponentModel.DataAnnotations 命名空間中的屬性定義的聲明性驗證規則,這意味著可以使用標准C#屬性特征表示驗證約束。 清單2-19顯示了如何將這些屬性應用於GuestResponse模型類。
Listing 2-19. 應用數據驗證
using System.ComponentModel.DataAnnotations;
namespace PartyInvites.Models {
public class GuestResponse {
[Required(ErrorMessage = "Please enter your name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter your email address")]
[RegularExpression(".+\\@.+\\..+",
ErrorMessage = "Please enter a valid email address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please enter your phone number")]
public string Phone { get; set; }
[Required(ErrorMessage = "Please specify whether you'll attend")]
public bool? WillAttend { get; set; }
}
}
MVC會自動檢測屬性,並在模型綁定過程中使用它們來驗證數據。 我導入了包含驗證屬性的命名空間,所以我可以引用它們,而不需要限定他們的名字。
提示: 如前所述,我為WillAttend屬性使用了可空的bool類型。 這樣做可以讓我應用必需的驗證屬性。 如果我使用了常規bool類型,我通過模型綁定收到的值可能只是真或假,我無法判斷用戶是否選擇了一個值。 可空的bool有三個可能的值:true,false和null。 如果用戶沒有選擇值,浏覽器會發送一個空值,這會導致Required屬性報告驗證錯誤。 這是一個很好的例子,演示MVC如何優雅地將C#功能與HTML和HTTP混合在一起。
我使用Controller類中的ModelState.IsValid屬性來檢查是否存在驗證問題。 清單2-20顯示了如何在Home控制器類的POST對應的RsvpForm行動方法中完成此操作。
Listing 2-20. 校驗表單中的錯誤
using System;
using Microsoft.AspNetCore.Mvc;
using PartyInvites.Models;
using System.Linq;
namespace PartyInvites.Controllers
{
public class HomeController : Controller
{
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View("MyView");
}
[HttpGet]
public ViewResult RsvpForm() {
return View();
}
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
if (ModelState.IsValid) {
Repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
} else {
// there is a validation error
return View();
}
}
public ViewResult ListResponses() {
return View(Repository.Responses.Where(r => r.WillAttend == true));
}
}
}
Controller基類提供了一個名為ModelState的屬性,它提供有關將HTTP請求數據轉換為C#對象的信息。如果ModelState.IsValue屬性返回true,那麼我知道MVC已經能夠滿足通過GuestResponse類中的屬性指定的驗證約束。當這種情況發生時,我就像以前一樣渲染了Thanks視圖。 如果ModelState.IsValue屬性返回false,那麼我知道有驗證錯誤。由ModelState屬性返回的對象提供了錯誤的詳細信息,但是我不需要進入該級別的詳細信息,因為我可以使用一個有用的功能,自動請求用戶解決任何通過調用View方法沒有任何參數的問題。當MVC呈現視圖時,Razor可以訪問與請求相關聯的任何驗證錯誤的詳細信息,標簽助手可以訪問詳細信息以向用戶顯示驗證錯誤。清單2-21顯示了向RsvpForm視圖添加驗證標簽助手屬性。
Listing 2-21. 增加驗證匯總
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<form asp-action="RsvpForm" method="post">
<div asp-validation-summary="All"></div>
<p>
<label asp-for="Name">Your name:</label>
<input asp-for="Name" />
</p>
<p>
<label asp-for="Email">Your email:</label>
<input asp-for="Email" />
</p>
<p>
<label asp-for="Phone">Your phone:</label>
<input asp-for="Phone" /></p>
<p>
<label>Will you attend?</label>
<select asp-for="WillAttend">
<option value="">Choose an option</option>
<option value="true">Yes, I'll be there</option>
<option value="false">No, I can't come</option>
</select>
</p>
<button type="submit">Submit RSVP</button>
</form>
</body>
</html>
將asp-validation-summary屬性應用於div元素,並在顯示視圖時顯示驗證錯誤列表。 asp-validation-summary屬性的值是一個名為ValidationSummary的枚舉的值,它指定了摘要將包含哪些類型的驗證錯誤。 我指定了All,這對於大多數應用程序來說都很適用,我將在第27章描述其他值並解釋它們中的工作原理。 要了解驗證摘要的工作原理,請運行應用程序,填寫“名稱”字段,並提交表單而不輸入任何其他數據。 您將看到驗證錯誤的摘要,如圖2-20所示。
圖2-20 顯示驗證錯誤
如果GuestResponse類的約束得沒有得到驗證,RsvpForm操作方法將不會呈現“感謝”視圖。 請注意,當Razor使用驗證摘要呈現視圖時,它會保留並顯示輸入到“名稱”字段中的數據。 這是模型綁定的另一個好處,它簡化了處理表單數據的工作。
注意: 如果你使用過ASP.NET Web Forms,你會知道Web Forms會將值序列化為名為__VIEWSTATE
的隱藏表單字段來保留服務器控件的狀態。 MVC將模型綁定到Web窗體服務器控件與View State的概念無關。 MVC不會在您呈現的HTML頁面中注入隱藏的__VIEWSTATE
字段。 而是通過設置輸入元素的值屬性來包含數據。
將模型屬性與元素相關聯的標簽助手屬性具有模型綁定的便捷功能。 當模型類屬性驗證失敗時,幫助器屬性將生成稍微不同的HTML。 以下是當沒有驗證錯誤時的為“電話”字段生成的輸入元素:
<input type="text" data-val="true" data-val-required="Please enter your phone number" id="Phone" name="Phone" value="">
相對的,這是在用戶在文本字段中不輸入任何數據並提交表單產生的HTML元素,(這是一個驗證錯誤,我將Required的驗證屬性應用到了GuestResponse類的Phone屬性上):
<input type="text" class="input-validation-error" data-val="true"
data-val-required="Please enter your phone number" id="Phone"
name="Phone" value="">
我已經高亮顯示了二者的區別:asp-for標簽幫助器屬性將輸入元素添加到一個名為input-validation-error的類中。 我創建一個包含此類的CSS樣式的樣式表,不同的HTML助手屬性使用的其他樣式表來實現這個效果。 MVC項目中的約定是將傳遞給客戶端的靜態內容放入wwwroot文件夾中,按內容類型進行組織,以便CSS樣式表進入wwwroot/css文件夾,JavaScript文件進入wwwroot/js文件夾,依此類推。 要創建樣式表,請右鍵單擊Visual Studio解決方案資源管理器中的wwwroot/css文件夾,選擇添加->新項,導航到客戶端部分,然後從模板列表中選擇樣式表,如圖2-21。
圖2-21 建立CSS樣式表
提示:當使用Web應用程序模板創建項目時,Visual Studio會在wwwroot / css文件夾中創建一個style.css文件。 你可以忽略這個文件,我在本章不使用它。
將文件的名稱設置為styles.css,單擊添加按鈕創建樣式表,然後編輯新文件,把他改成代碼2-22所示的樣式。
Listing 2-22. styles.css 文件的內容
.field-validation-error {color: #f00;}
.field-validation-valid { display: none;}
.input-validation-error { border: 1px solid #f00; background-color: #fee; }
.validation-summary-errors { font-weight: bold; color: #f00;}
.validation-summary-valid { display: none;}
To apply this stylesheet, I have added a link element to the head section of the RsvpForm view, as shown
in Listing 2-23 .
Listing 2-23. Applying a Stylesheet in the RsvpForm.cshtml File
...
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
...
link 元素使用href屬性來指定樣式表的位置。 請注意,URL中省略了wwwroot文件夾。 ASP.NET的默認配置包括支持靜態內容,如圖像,CSS樣式表和JavaScript文件,並將請求自動映射到wwwroot文件夾。 我在第14章中描述了ASP.NET和MVC配置過程。
提示:有一個特殊的標簽幫助器來處理樣式表,如果你有很多文件要管理,這可以很有用。 詳見第25章。
隨著樣式表的應用,當提交導致驗證錯誤的數據時,會顯示更明顯的驗證錯誤,如圖2-22所示。
圖2-22 自動高亮錯誤
應用程序的所有功能目標都完成了,但應用程序的整體外觀還需要調整一下。 當您使用Web應用程序模板創建項目時,如本章中的示例所示,Visual Studio安裝了一些常見的客戶端開發包。 雖然我不是使用模板的粉絲,但我喜歡Microsoft選擇的客戶端庫。 其中一個被稱為Bootstrap,它是一個很好的CSS框架,最初由Twitter開發,已經成為一個主要的開源項目,它已經成為Web應用程序開發的支柱。
注意:Bootstrap 3是我寫的當前版本,但是第4版正在開發中。 Microsoft可能會選擇在Visual Studio的更高版本中更新Web應用程序模板使用的Bootstrap版本,這可能會導致內容顯示不同。 這對於本書中的其他章節來說不會是一個問題,因為我向您展示如何明確指定包版本,以便獲得預期的結果。
wwwroot/lib/bootstrap文件夾的文件中定義了一些CSS選擇器,基本的Bootstrap功能通過將類應用到與添加到CSS選擇器相對應的元素來工作。 您可以從http://getbootstrap.com 獲取Bootstrap定義的類的詳細信息,代碼2-24演示了如何將一些基本樣式應用於MyView.cshtml視圖。
Listing 2-24. 給MyView.cshtml添加Bootstrap
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<div class="text-center">
<h3>We're going to have an exciting party!</h3>
<h4>And you are invited</h4>
<a class="btn btn-primary" asp-action="RsvpForm">RSVP Now</a>
</div>
</body>
</html>
我添加了其中link元素,他的href屬性是wwwroot/lib/bootstrap/dist/css/bootstrap.css。 第三方CSS和JavaScript包安裝在wwwroot/lib文件夾中是一個常用約定,我將在第6章中描述用於管理這些包的工具。 導入Bootstrap樣式表後,我需要為我的元素設置樣式。 這是一個簡單的例子,所以我只需要使用少量的Bootstrap CSS類:text-center,btn和btn-primary。 text-center class將元素及其子元素的內容置於中心位置。 btn類將按鈕、input元素變得更漂亮,btn-primary指定我想要的按鈕的顏色范圍。 運行應用程序可以看到效果,如圖2-23所示。
圖2-23 設置視圖樣式
很明顯,我不是網頁設計師。 事實上,作為一個孩子,我根本就沒有任何才華,因此我被排除在藝術課之外。 這使得我有更多時間來上數學課,但意味著我的藝術能力並沒有超過10歲的平均水平。 對於一個真正的項目,我會尋求一位專業人士來幫助設計和設計內容,但是在這個例子中,我一個人就這樣做了,也就是應用Bootstrap可以像我一樣有一些限制和一致性。
Bootstrap定義了可以用於樣式表單的類。 我不會詳細介紹他們,但是您可以看到我已經在清單2-25中應用了這些類。
Listing 2-25. 給RsvpForm.cshtml添加Bootstrap
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<div class="panel panel-success">
<div class="panel-heading text-center"><h4>RSVP</h4></div>
<div class="panel-body">
<form class="p-a-1" asp-action="RsvpForm" method="post">
<div asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="Name">Your name:</label>
<input class="form-control" asp-for="Name" />
</div>
<div class="form-group">
<label asp-for="Email">Your email:</label>
<input class="form-control" asp-for="Email" />
</div>
<div class="form-group">
<label asp-for="Phone">Your phone:</label>
<input class="form-control" asp-for="Phone" />
</div>
<div class="form-group">
<label>Will you attend?</label>
<select class="form-control" asp-for="WillAttend">
<option value="">Choose an option</option>
<option value="true">Yes, I'll be there</option>
<option value="false">No, I can't come</option>
</select>
</div>
<div class="text-center">
<button class="btn btn-primary" type="submit">
Submit RSVP
</button>
</div>
</form>
</div>
</div>
</body>
</html>
此示例中的Bootstrap類創建一個標題,只給了布局的結構。 要設置樣式,我使用了form-group類,用於給標簽元素和相關的input或select元素設置樣式。 您可以在圖2-24中看到效果。
圖2-24 設置RsvpForm視圖的樣式
下一個要設置樣式的視圖是Thanks.cshtml,您可以使用類似於我用於其他視圖的CSS類來看清楚如何在代碼2-26中完成此操作。 為了使應用程序更易於管理,盡可能避免重復代碼和標記,這一個很好的原則。 MVC提供了幾個功能來幫助減少重復,我將在後面的章節中介紹。 這些功能包括Razor布局(第5章),部分視圖(第21章)和視圖組件(第22章)。
Listing 2-26. 設置Thanks.cshtml樣式
@model PartyInvites.Models.GuestResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Thanks</title>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body class="text-center">
<p>
<h1>Thank you, @Model.Name!</h1>
@if (Model.WillAttend == true) {
@:It's great that you're coming. The drinks are already in the fridge!
} else {
@:Sorry to hear that you can't make it, but thanks for letting us know.
}
</p>
Click <a class="nav-link" asp-action="ListResponses">here</a>
to see who is coming.
</body>
</html>
圖2-25 顯示了樣式的效果。
圖2-25 Thanks視圖的樣式
最後的風格是ListResponses,它列出了與會者的列表。 對內容設置樣式遵循與所有Bootstrap樣式相同的基本方法,如代碼2-27所示。
Listing 2-27. ListResponses.cshtml 加 Bootstrap
@model IEnumerable<PartyInvites.Models.GuestResponse>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
<title>Responses</title>
</head>
<body>
<div class="panel-body">
<h2>Here is the list of people attending the party</h2>
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
@foreach (PartyInvites.Models.GuestResponse r in Model) {
<tr>
<td>@r.Name</td>
<td>@r.Email</td>
<td>@r.Phone</td>
</tr>
}
</tbody>
</table>
</div>
</body>
</html>
圖2-26顯示了與會者的表格的呈現方式。 將這些樣式添加到視圖中完成了示例應用程序,該應用程序現在實現了所有的開發目標,並且外觀也已經改的很好了。
圖2-26 ListResponses視圖的樣式
在本章中,我創建了一個新的MVC項目,並使用它來構建一個簡單的數據錄入應用程序,讓您首先了解ASP.NET Core MVC架構和方法。 我跳過了一些關鍵的東西(包括Razor語法,路由和測試),但我將在後面的章節深入介紹這些內容。 在下一章中,我將描述MVC設計模式,這是開發ASP.NET Core MVC應用程序的基礎。