一、Controller的責任
MVC的核心就是Controller(控制器),它負責處理瀏覽器傳送過來的所有請求,並決定要將什么內容響應給瀏覽器。但Controller並不負責決定內容應該如何顯示,而是將特定形態的內容響應給MVC架構,最后才由MVC架構依據響應的形態來決定如何將內容響應給瀏覽器。如何決定響應內容是View的責任。
二、Controller的類與方法
Controller本身就是一個類(Class),該類有許多方法(Method)。在這些方法中,只要是公開方法,該方法就會被視為是一種動作(Action);只要有動作存在,就可以通過該動作方法接收網頁請求並決定響應視圖。
由上可知編寫Controller的基本要求:
- Controller必須為公開類。
- Controller的名稱必須以"Controller"結尾。
- 必須繼承自MVC內置的Controller類,或繼承自實現IController接口的自定義類,或自行實現IController接口。
- 所有方法必須為公開方法。改方法可以沒有參數,也可以有多個參數。
三、Controller的執行過程
Controller被MvcHandler選中之后,下一步就是通過ActionInvoker選取適當的Action來執行。在Controller中,每一個Action可以定義0到多個參數。ActionInvoke會依據當前的RouteValue及客戶端傳過來的信息准備好可輸入Action參數的依據,最后正式調用被Controller選中的那個Action方法。
Action執行完后的返回值通常是ActionResult類的。事實上,ActionResult類是一個抽象類,因此,MVC本身就實現了許多不同ActionResult類的子類。Controller得到ActionResult類之后,就會開始執行ActionResult類的ExecuteResult()方法,並將執行的結果返回客戶端。這時,Controller的任務就算完成了。
Controller在執行時還有一個動作過濾器(Action Filter)機制,可以分成以下4中類型。
- 授權過濾器(Authorization Filter);
- 動作過濾器(Action Filter);
- 結果過濾器(Result Filter);
- 例外過濾器(Exception Filter)。
此外,在Controller的執行過程中還必須考慮動作過濾器的執行順序。除上述說明之外,在執行Action與ActionResult類時嗨會有一些事件被執行,這部分將在九中說明。
四、動作名稱選取器
通過ActionInvoker選取Controller中的公開方法時,默認會用Reflection(映像)的方式取得Controller中具有相同名字的方法(不區分大小寫)。如下程序范例表示得很清楚:當RouteValue表達式中的Action是"Index",默認會執行Index()方法。
1 public class HomeController : Controller 2 { 3 /// <summary> 4 /// 要求網址 http://localhost/Home/Index 5 /// </summary> 6 public ActionResult Index() 7 { 8 return View(); 9 } 10 11 }
如果在以上的Action中加入ActionName屬性,並將其指名為"Default",此時,若RouteValue表達式中的Action是"Index",就不會執行Index()方法,而必須使RouteValue表達式中的Action為"Default",Index()方法才能被正確執行,這就是動作名稱選取器(Action Name Selector)的作用,示例如下。
1 public class HomeController : Controller 2 { 3 /// <summary> 4 /// 要求網址 http://localhost/Home/Index 5 /// </summary> 6 [ActionName("Default")] 7 public ActionResult Index() 8 { 9 return View(); 10 } 11 12 }
唯一需要特別注意的是,如果你使用默認的"return View()"方法返回ActionResult類,由於應用了[ActionName("Default")]屬性,所以MVC會去尋找"/Views/Home/Default.aspx"頁面而不是"/Views/Home/Index.aspx"頁面來執行。
五、動作方法選取器
5.1 NonAction屬性
若將NonAction屬性應用在Controller中的Action方法上,即便該Action方法是公開方法,也會告知ActionInvoke不要選取這個Action來執行。這個屬性主要用來保護Controller中的特定公開方法不會被發布到Web上。或是當功能尚未開發完成就要進行部署時,若暫時不想將此方法刪除,也可應用這個屬性,表示"不要對外公開"。
1 [NonAction] 2 public ActionResult Index() 3 { 4 return View(); 5 }
將Action方法中的"public"修改成"privare",也可以達到同樣的目的,示例如下:
1 private ActionResult Index() 2 { 3 return View(); 4 }
5.2 HttpGet屬性、HttpPost屬性、HttpDelete屬性和HttpPut屬性
HttpGet、HttpPost、HttpDelete和HttpPut屬性是動作方法選取器的一部分,我們以下列程序為例進行介紹。若應用了[httpPost]屬性,表示只有當客戶端瀏覽器發送HTTP POST請求時才可以選取這個Action。
1 [HttpPost] 2 private ActionResult Index() 3 { 4 return View(); 5 }
相反的,若果沒有應用這些屬性,客戶端瀏覽器發送任何HTTP動詞,都會自動選取對應的Action。
這些屬性常用在需要接受窗口數據的時候。你可以創建兩個同名的Action,一個應用[HttpGet]屬性來顯示窗口HTML,另一個應用[HttpPost]屬性來接收窗口送出的值,范例程序如下。
1 [HttpGet] 2 public ActionResult Create() 3 { 4 return View(); 5 } 6 [HttpPost] 7 private ActionResult Create(FormCollection c) 8 { 9 UpdateToDB(c); 10 return RedirectToAction("Index"); 11 }
NOTE 由於HTML窗口無法送出"Delete"這個Http動詞,所以如果希望Action能提供像RESET協議那樣的方式來處理刪除動作,又能通過同一個窗口使用這個只允許"Delete"的動作的話,可以用Html.HttpMethodOverride()方法的HTML輔助方法來模擬Http Delete方法的行為,但實際上窗口還是以Http Post的方式送出去的。
六、 ActionResult類
ActionResult類是Action執行的結果,但ActionResult中並不包含執行結果,而是包含執行響應時所需的信息。當Action返回ActionResult類之后,會由MVC執行。先看看ActionResult抽象類的程序代碼。在ActionResult抽象類中僅定義了一個ExecuteResult()方法來執行結果。
1 namespace System.Web.Mvc 2 { 3 // 摘要: 4 // 封裝一個操作方法的結果並用於代表該操作方法執行框架級操作。 5 public abstract class ActionResult 6 { 7 // 摘要: 8 // 初始化 System.Web.Mvc.ActionResult 類的新實例。 9 protected ActionResult(); 10 11 // 摘要: 12 // 通過從 System.Web.Mvc.ActionResult 類繼承的自定義類型,啟用對操作方法結果的處理。 13 // 14 // 參數: 15 // context: 16 // 用於執行結果的上下文。上下文信息包括控制器、HTTP 內容、請求上下文和路由數據。 17 public abstract void ExecuteResult(ControllerContext context); 18 } 19 }
MVC定義的ActionResult如表所示:
類 |
Contro輔助方法 |
用 途 |
ContentResult |
Content |
返回一段用戶自定義的文字內容 |
EmptyResult |
|
不返回任何數據,即不響應任何數據 |
JsonResult |
Json |
將數據序列化成JSON格式返回 |
RedirectResult |
Redirect |
重定向到指向的URL |
RedirectToRouteResult |
RedirectToAction、RedirectToRoute |
與RedirectResult類似,但它將新定向到一個Action或Route |
ViewResult |
View |
使用IViewInstance接口和IViewEngine接口,實際輸出數據的是IViewEngine接口和View |
PartialViewResult |
PartialView |
與ViewResult類相似,返回的是”部分顯示”,即”UserControls”目錄下的View |
FileResult |
File |
以二進制串流的方式返回一個文件數據 |
JavaScriptResult |
JavaScript |
返回的是JavaScript指令碼 |
表中的Controller輔助方法在Controller類中為返回ActionResult類提供支持,如下程序可用於跳轉到另一個頁面。
1 [HttpPost] 2 public ActionResult Post(FormCollection c) 3 { 4 return new RedirectResult("/"); 5 }
如果使用Controller輔助方法,就可以將以上程序改寫如下:
1 [HttpPost] 2 public ActionResult Post(FormCollection c) 3 { 4 return Redirect("/"); 5 }
以上兩段程序代碼其實差不多,但實際操作中則是以使用Controller輔助方法居多。
6.1 ViewResult類
ViewResult類是在MVC中最常用的ActionResult類,用於返回一個標准的視圖。通過Controller輔助方法,我們能更方便地定義如何輸出視圖。指定要輸出的View名稱、指定該View要應用哪個MasterPage、指定要輸入的View的Model。
6.2 PartialViewResult類
PartialViewResult類與ViewResult類非常相似,但它無法為View賦值MasterPage,通常用在前端為Ajax應用程序的情況下,並可以通過Ajax來取得網頁中的部分內容。
如下程序會執行"/Views/Home/About.ascx"頁面,並將結果輸出至客戶端。
1 public ActionResult About() 2 { 3 return PartialView(); 4 }
6.3 EmptyResult類
有些Action在執行后其實不需要返回任何數據,例如一個頁面執行完后直接轉到其他頁面的情況。EmptyResult類不會執行任何響應客戶端的程序,所以也不會返回任何數據,使用方法如下:
1 public ActionResult Empty() 2 { 3 return new EmptyResult(); 4 }
在MVC中,還有一種表達EmptyResult類的方式,即將上述語法寫成如下:
1 public void Empty() 2 { 3 return; 4 }
有一種情況是用EmptyResult類搭配Response.Redirect()方法進行HTTP 302暫時轉向,示例如下:
1 public void Redirect() 2 { 3 Response.Redirect("/Home/Index"); 4 }
如果已經開始使用.NET 4.0,也可以考慮使用4.0新增的Respo.RedirectPermanent()方法建立HTTP 301永久轉向,示例如下。
1 public void Redirect() 2 { 3 Response.RedirectPermanent("/Home/Index"); 4 }
6.4 ContentResult類
ContentResult類可以響應文字內容的結果。可以讓ContentResult類響應任意指定文字內容。Content-Type和文字編碼(Encoding)。
如下范例將會響應一段XML文字,並設定響應的Content-Type為text/xml(已指定響應的文字編碼方式為Encoding.UTF8)。
1 public ActionResult Content() 2 { 3 return Content("<ROOT><TEXT>123</TEXT></ROOT>","text/xml",Encoding.UTF8); 4 }
如果只響應一串HTML字符串,可以只使用第一個參數,如下:
1 public ActionResult Content() 2 { 3 string strHTML = "........"; //省略 HTML的內容 4 return Content(strHTML); 5 }
還有一種方法可以表達如上一樣簡單的返回類,直接將返回類設定成"string"即可。MVC會進行判斷,只要Action返回的不是ActionResult類,就會將返回的類轉換成字符串類輸出。
6.5 FileResult類
FileResult類可以響應任意的文件內容,包括二進制格式的數據,例如圖像文件。PDF文檔或ZIP文件,可以輸入byte數組、文件路徑、stream數據、Content-Type、下載文件名等參數並將其返回客戶端。事實上,FileResult是一個抽象類,在MVC中實現FIleResult類的子類共有3個,分別是:
- FilePathResult:響應一個實體文件;
- FileContentResult:響應一個byte數組的內容;
- FileStreamResult:響應一個Stream數據。
但通過Controller類中所提供的File輔助方法可以讓你不用記憶這么多。File()輔助方法能自動選取不同的FileResult類進行響應。
如果通過Action輸出一個存儲在"App_Data"目錄欄下的PNG圖像文件,可以參考以下代碼:
1 public ActionResult GetFile() 2 { 3 return File(Server.MapPath("~/App_Data/UserA/a.png"),"image/png"); 4 }
若希望直接通過瀏覽器下載文件,而不是在瀏覽器打開文件,可以再第3個參數中輸入要求下載的文件名。如:PDF文件來自於數據庫,並希望讓用戶下載,可以先取得一個byte數組或Stream數據,並在File()輔助方法的第2個參數中指定正確的Content-Type,最后再指定要下載的文件名即可。
1 public ActionResult GetFile() 2 { 3 byte[] fileContent = GetFileByteArrayFromDB(); 4 5 return File(fileContent,"application/pdf","Report.pdf"); 6 }
6.6 強制下載文件時需注意中文文件名的問題
由於MVC支持中文文件名文件下載的能力有限,若要開發出跨瀏覽器下載中文文件名文件的解決方案,就不能只靠FileResult類了,可以自定義ActionResult類來克服這個問題,也可直接用較為底層的方法來解決。一下將說明強制下載文件的原理及解決方法。
強制下載文件功能時,通常是通過設定"Content-Disposition"這個HTTP響應標頭(Response Header)的方式將強制下載文件的要求告知客戶端,示例如下。
1 string fileName = "ExportData.csv"; 2 string strContentDisposition = String.Format("{0};filename=\"{1}\"", "attachment", fileName); 3 Response.AddHeader("Content-Disposition", strContentDisposition);
通過上述程序代碼,就可以讓客戶端強制下載此頁面的內容,也就是說,改頁面的內容(可能是文件或二進制文件)不會直接在瀏覽器中打開,下載后也不會打開相關程序。
Content-Disposition標頭的第一組參數是"attachment",代表此文件是一個附件文件,也就是"要求下載"的意思。如果將"attachment"改成".inline"的話,就代表這是一個內嵌於其他網頁的文件(如圖像文件、CSS、JavaScript、Flash等),而這也是默認的設定,等同於不添加Content-Disposition標頭的情況。
6.7 JavaScriptResult類
JavaScriptResult類的用途是將javaScript程序代碼響應給瀏覽器。通過Ajax環境,可以利用JavaScriptResult類來響應適當的JavaScript程序代碼並將其交給瀏覽器動態執行,由於Ajax功能屬於View.
6.8 JsonResult類
JSON是Web在實現Ajax應用程序時經常使用的一種數據傳輸格式,JsonResult類可自動將任意對象的數據序列轉換成JSON格式返回。JsonResult類默認的Content-Type為application/json,對於某些JavaScript Framework來說,這是必要的需求,如jQuery。 建議盡量避免使用HTTP GET方法獲取JSON數據。但若只使用HTTP POST方法獲取JSON數據也存在一個問題,那就是數據無法被瀏覽器緩存。如果數據敏感度不高且想實現緩存的話,還需讓JsonResult類能夠對HTTP GET請求進行響應,解決方案就是為JSON()輔助方法加上一個JsonResultBehavior列舉參數,這樣就可以通過GET方法取得JSON數據了。
1 public ActionResult JSON() 2 { 3 return Json(new 4 { 5 id = 1, 6 name = "will", 7 CreatedOn = DateTime.Now 8 }, JsonRequestBehavior.AllowGet); 9 }
6.9 RedirectResult類
RedirectResult類主要用途是執行指向其他頁面的重定向。在RedirectResult類的內部,基本上還是用Response.Redirect()方法響應HTTP 302暫時定向的。
1 public ActionResult Redirect() 2 { 3 return Redirect("/Home/Index"); 4 }
6.10 RedirectToRoute類
Controller類中有兩個與RedirectToRoute類有關的輔助方法,分別是RedirectToAction()和RedirectToRoute().
1. RedirectToAction()輔助方法
RedirectToAction()方法比較簡答,既可通過直接輸入Action名稱來設定讓瀏覽器轉向該Action網址,也可以輸入新建的RouteValue值,如下:
- 轉到同一個Controller中的另一個Action。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("SamplePage"); 4 }
- 轉到指定Controller的特定Action。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("List", "Member"); 4 }
- 轉到MemberController的List Action,並且加上"page"這個RouteValue值。
1 public ActionResult RedirectToActionSample() 2 { 3 return RedirectToAction("List", "Member", new { page = 3 }); 4 }
2. RedirectToRoute()輔助方法
RedirectToRoute()輔助方法較為復雜,可利用Global.asax文件中定義的網址路由表來指定不同的轉向網址。
- 轉到同一個Controller中的另一個Action。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { action = "SamplePage" }); 4 }
- 轉到指定Controller的特定Action。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { controller = "Member", action = "list" }); 4 }
- 轉到MemberController的List Action,並且加上"page"這個RouteValue。
1 public ActionResult RedirectToRouteSample() 2 { 3 return RedirectToRoute(new { controller = "Member", action = "list", page = 3 }); 4 }
七、ViewData與TempData概述
7.1 ViewData
ViewData屬性是一個ViewDataDictionary類,可用於存儲任意對象的數據,但存儲的鍵值必須為字符串。
ViewData有一個特性,就是它只會存在於當前的HTTP請求中,而不像Session一樣可以講數據帶到下一個HTTP請求。
7.2 TempData
TempData的數據結構與ViewData一樣,都屬於字典類,不過TempData屬性的類是TempDataDictionary。TempData嗨有一點不一樣的地方,就是它的內部是用Session來存儲數據的。存儲在TempData中的數據只會暫存:1次網頁請求!
1次網頁請求的定義:當窗口數據被傳送到以下Action中進行存儲時,如果發生新建數據失敗的情況,我們會希望這次傳送的數據可以保留至下一個頁面,此時,就會將這個只希望出現1此的信息保存到TempData中,並在下一個頁面中執行。
1 [HttpPost] 2 public ActionResult Create(Message msg) 3 { 4 if (!UpdateMessageToDB(msg)) 5 { 6 TempData["PostedMessage"] = msg; 7 return RedirectToAction("Create"); 8 } 9 return RedirectToAction("Index"); 10 }
當新建數據失敗的情況發生后,將會返回"Create"這個Action,並將原來的數據從TempData中讀出,示例如下:
1 [HttpGet] 2 public ActionResult Create() 3 { 4 string data = TempData["PostedMessage"] as Message; 5 return View(data); 6 }
此時,數據就會回到Create()方法,並在此被傳送到Create視圖。在這個Controller生命周期結束的前一刻,由於MVC會記錄"TempData["PostedMessage"]"語句已經被讀取了,因此,在網頁請求結束之前會將"TempData["PostedMessage"]"語句刪除。
八、模型綁定
8.1 簡單模型綁定
8.2 使用FormCollection類獲取窗口數據
除了可以通過簡單模型綁定機制獲取窗口傳過來的單欄數據之外,我們也可以通過FormCollection類一次獲取整個窗口傳送過來的數據。只要設定一個FormCollection類的參數,就可以獲取所有從窗口傳送過來的數據。這種用法與使用Request.Form()方法一樣,不過在MVC里還是建議盡量不要用Request.Form()方法來獲取窗口數據。
1 public ActionResult GetFormCollection(FormCollection form) 2 { 3 ViewData["uName"] = form["uName"]; 4 return View("ModelBinderDemo"); 5 }
8.3 復雜模型綁定
8.4 多個復雜模型的綁定
好比一個HTML代碼中只有一個Form(表單)窗口,但是該窗口內有兩組字段,是因為在編寫數據綁定類里面設定了兩組參數,分別為form1和form2,而這兩組參數的類都是GuestbookForm,如:
1 public Action ComplexModelBinding(GuestbookForm form1, GuestbookForm form2) 2 { 3 InsertIntoDB(form1); 4 InsertIntoDB(form2); 5 return Redi("/"); 6 }
只需要做好模型綁定即可。
8.5 判斷模型綁定的結果
驗證數據,用自身的方法如Html.ValidationMessageFor()方法用來顯示特定字段的錯誤信息。也可在Controller中判斷業務邏輯。
九、動作過濾器(略)