整個web應用平臺的關注點在於構建並顯示動態輸出內容。在MVC裏,控制器負責構建一些數據並將其傳給視圖。視圖負責渲染成HTML。 從控制器向視圖傳遞數據的一種方式是使用ViewBag 對象,它是一個控制器基類的成員。ViewBag是一個動態對象,你能夠給他賦值任意屬性給視圖來渲染用。代碼2-5 演示瞭如何在HomeController裏傳遞簡單對象。 Listing 2-5. 設置視圖數據css
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"); } } } html
我向ViewBag.Greeting屬性賦值,以給視圖提供數據。Greeting屬性在賦值以前是不存在的,這容許我以任意流暢的方式從控制器向視圖傳遞數據而沒必要在賦值以前定義類。我在視圖中引用了ViewBag.Greeting屬性以得到他的值。如同代碼2-6,這是修改後的MyView.cshtml。 web
Listing 2-6. 在視圖裏獲取傳遞過來的值bootstrap
@{ 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,因此我將略過去一些講解有些東西的內部原理。可是不要擔憂,我將會在之後的章節中討論那些內容的。 app
想像一下,一個朋友決定了要舉行一個新年晚會,他請我創建一個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.
在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應用程序的基礎。