控制器是包含必要的處理請求的.NET類,控制器的角色封裝了應用程序邏輯,控制器主要是負責處理請求,實行對模型的操做,選擇視圖呈現給用戶。javascript
簡單理解:實現了IController接口,修飾符必須是public,不能是抽象的,不能是泛型的,類名必須以Controller結尾。html
在MVC框架中,控制器類必須實現System.Web.Mvc命名空間下的IController接口,如上圖所示,這是一個很是簡單的接口,該接口僅有一個Execute方法,當請求該控制器時Execute方法被調用。經過實現IController接口,你能夠建立控制器類。java
a、每個針對應用程序的請求,都是經過控制器自由地選擇合適的方式來處理的,只要它不偏離到視圖(View)和模型(Model)所負責的區域。編程
b、不要把業務或數據存儲的邏輯放到控制器裏面,也不要建立用戶接口。json
示例: 建立一個實現Icontroller接口的類,讀取路由數據,並生成數據寫入響應。設計模式
在Controllers文件夾下建立一個名爲MyFirstController的類,實現IController接口並添加以下代碼瀏覽器
運行該應用程序並在地址欄導航到/MyFirst,即可以看到此控制器產生的輸出。服務器
建立一個類經過實現IController接口,MVC框架會將其視爲一個控制器,並將請求發送給它,並且在如何處理和響應請求上沒有任何限制,這是一個很好的示例,由於它向你展現了MVC框架的可擴展性,但用這種方式編寫一個複雜的應用程序是很是困難的。數據結構
經過System.Web.Mvc.Controller類你能夠派生你的控制器,System.Web.Mvc.Controller類是大多數Web開發人員須要熟悉的,用來對請求處理提供支持的一個類,Controller提供瞭如下三個關鍵特性。併發
(1)、動做方法(Action Method):一個控制器的行爲被分解成多個方法(而並不是只有惟一的Execute()方法)。每一個動做方法被暴露給不一樣的URL,並經過從輸入請求提取的參數進行調用。
(2)、動做結果(Action Result):你能夠返回一個描述動做結果的對象(例如:渲染一個視圖,或重定向到一個不一樣的URL或動做方法),而後經過該對象實現你的目的。這種指定結果和執行之間的分離簡化了單元測試。
(3)、過濾器(Filter):你能夠把可重用的行爲封裝成過濾器,而後經過在代碼中添加特性的的方式,把這種行爲標註到一個過多個控制器或動做方法上。
除非在頭腦中有一個很是明確的需求,不然建立控制器最好的辦法就是經過Controller類進行派生,這也正是你在Visual Studio中添加一個控制器,Visual Studio爲你所作的事情。
在Controllers文件夾下建立一個名爲MySecondController的類,繼承與Controller類,而後添加一個動做方法TestAction並編寫以下代碼返回一個動做結果,最後再該動做方法內右鍵添加對應的視圖。
運行應用程序並導航到/MySecond/TestAction瀏覽結果以下:
做爲Controller類的一個派生類,所要作的工做是實現動做方法、獲取所須要的各類輸入,以對請求進行處理,並生成一個適當的響應。後面的內容將介紹數據的接收與響應。
a、查詢字符串值 b、表單數據 c、路由數據
控制器須要常常訪問來自輸入請求的數據,如查詢字符串、表單數據、以及由路由系統根據輸入的URL解析獲得的參數的值。訪問這些數據有三種主要方式。
(1)、從上下文對象提取。
(2)、做爲參數被傳遞給動做方法(Action Method)而造成的數據。
(3)、明確調用框架的模型綁定(Model Binding)功能。
注意:參數名稱是忽略大小寫的,如Request["Test"]與Request["test"]結果是同樣的。以下圖:
View部分
Controller部分
當建立一個從Controller基類派生的控制器時,就可以訪問一組很是便利的屬性,這些屬性包括:Request、Response、RouteData、HttpContext、Server等,每個屬性都包含了請求不一樣方面的的信息。在Action方法裏是可使用任何Context對象來訪問這些屬性。例如:
1 public ActionResult Index() 2 { 3 string userName = User.Identity.Name; 4 string serverName = Server.MachineName; 5 string clientIP = Request.UserHostAddress; 6 DateTime dateStamp = HttpContext.Timestamp; 7 8 string oldProductName = Request.Form["OldName"]; 9 string newProductName = Request.Form["NewName"]; 10 11 ViewBag.Message = "本機的IP是:" + clientIP; 12 return View(); 13 }
能夠利用VS智能感知,在動做方法中輸入this.找到這些可用的上下文信息。
下面的方式是否是比上面的方法更優雅易讀呢?不過須要注意的是:Action方法裏面是不容許有ref或out參數的,雖然編譯不會報錯但運行時會拋出一個異常。以下所示。
MySecond控制器代碼以下:
Index視圖代碼以下:
運行結果以下:
Controller基類使用叫作「值提供器(Value Provider)」和「模型綁定器(Model Binder)」的MVC框架組件來獲取動做方法的參數值。值提供器將可用的數據項集合呈現給控制器。有一組內建的值提供器從Request.For
m、Request.QueryString、Request.Files、RouteData.Values獲取數據項,而後這些值會被傳遞給模型綁定器,模型綁定器會嘗試將這些數據映射成動做方法參數的數據類型。默認的模型綁定可以建立和填充任何.NET類型的對象,包括自定義的類型和集合。
若是MVC框架找不到引用類型參數(如:string或object)的值,動做方法仍然會被調用,但對該參數會使用一個null值,若找不到值類型參數(如:int或double)的值則會拋出一個異常,而且不會調用動做方法。
a、值類型參數是必須被賦值的。若是想讓此參數和引用類型參數同樣,能夠定義成int?如:public ActionResult Index(int? num),當依然沒有值時,不會發生異常,而是會傳遞null值。
b、引用類型的參數是可選的。爲了使它成爲必須的(保證一個非空的值被傳遞),在動做(Action)方法上添加一些代碼拒絕null。例如,在該值等於null時,拋出一個ArgumentNullException異常。
若是但願處理不含動做方法參數的請求,但又不想在代碼中檢查null值或拋出異常,可使用C#的可選參數特性來代替。以下所示:
1 public ActionResult List(string query = "all", int page = 1) 2 { 3 //此處省略代碼N行... 4 return View(); 5 }
在定義參數時,經過對參數賦值的辦法,能夠將參數標記爲可選的,如上訴代碼中,給query和page參數提供了默認值。MVC框架會試圖經過請求爲這些參數獲取值,但若是沒有值可用,那麼將用所指定的默認值代替。
對於string類型參數query,注意string類型是引用類型,這意味着不須要檢查null值。若是請求爲指定查詢字符串,那麼該動做(Action)方法將以字符串「all」進行調用。對於int類型參數,注意int類型是值類型,在沒有page值時,請求不會致使錯誤,該方法將以默認值「1」進行調用。
原理:
a、MVC框架接收從Action方法返回的ActionResult對象,並調用定義在ActionResult類裏面ExecuteResult方法,對ActionResult進行實現,爲咱們處理Response對象並生成相關的輸出。
b、MVC框架內置了許多ActionResult類型,都是從ActionResult類派生的,可以方便咱們在Action方法裏面選擇具體的返回類型,好比要呈現到View,能夠選擇ViewResult做爲Action方法的返回值。
MVC框架含有許多內置的動做結果類型以下圖所示,全部這些類型都是派生於ActionResult,其中在Controller類中有便利的輔助器方法。下面將解釋如何使用這些結果類型。
MVC框架支持的輸出類型有: 1.視圖 2.文本數據 3.XML數據 4.JSON數據 5.文件或者二進制數據 6.返回錯誤和HTTP Codes 7.定製的ActionResult 8.重定向
最多見的來自Action方法的響應就是生成HTML併發送給瀏覽器,爲了使用動做結果(ActionResult)生成HTML,須要建立一個指定了要呈現的視圖ViewResult類的實例。咱們在Home控制器中編寫以下代碼.代碼中指定了HomePage的視圖。
當MVC框架調用ViewResult對象的ExecuteResult時,就會開始對指定的View進行搜素。
使用了區域(Area),則搜索順序以下:
一、/Area/<AreaName>/Views/<ControllerName>/<ViewName>.aspx
二、/Area/<AreaName>/Views/<ControllerName>/<ViewName>.ascx
三、/Area/<AreaName>/Views/Shared/<ControllerName>/<ViewName>.aspx
四、/Area/<AreaName>/Views/Shared/<ControllerName>/<ViewName>.ascx
五、/Area/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml
六、/Area/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml
七、/Area/<AreaName>/Views/Shared/<ControllerName>/<ViewName>.chtml
八、/Area/<AreaName>/Views/Shared/<ControllerName>/<ViewName>.vbhtml
若是沒有使用區域(Area)或者在前面找不到則會在下面搜索,搜索順序以下:
一、/Views/<ControllerName>/<ViewName>.aspx
二、/Views/<ControllerName>/<ViewName>.ascx
三、/Views/Shared/<ControllerName>/<ViewName>.aspx
四、/Views/Shared/<ControllerName>/<ViewName>.ascx
五、/Views/<ControllerName>/<ViewName>.cshtml
六、/Views/<ControllerName>/<ViewName>.vbhtml
七、/Views/Shared/<ControllerName>/<ViewName>.chtml
八、/Views/Shared/<ControllerName>/<ViewName>.vbhtml
只要有一個視圖找到,就中止搜索,並開始將找到的視圖(View)呈現給客戶端。
經過路徑來指定呈現的視圖(View)
這種命名約定的方法很是方便和簡捷,可是它限制了咱們所能呈現的一些視圖。若是要呈現一個具體的視圖,能夠提供一個明確的路徑,下面是一個例子。
當咱們這樣指定一個視圖時,指定的路徑必須以「/」或者「~/」而且包含擴展名(如:.aspx)。固然不推薦這樣來使用,由於這不利於程序的擴展和維護,這是一種綁定或耦合,有違MVC設計思想,能夠有其餘的方法來達到一樣的效果例如:使用RedirectToAction()方法。
View輔助方法
View():返回到Action同名的視圖。
View(「viewName」):返回到此控制器的ViewName視圖。
View(「~/views/othercontroller/viewname.cshtml」):這種方式必須以~/或者/開頭,但並不建議這麼作,由於能夠調用RedirectToAction這樣的方法。
View(「viewname」,」layout」):呈現這個視圖的時候,換一個母版頁。
傳輸數據的幾種方式:1.視圖模型 2.ViewData 3.ViewBag 4.TempData
將一個對象做爲View方法的參數傳遞給視圖。例如:
Controller部分
1 public ViewResult Index2() 2 { 3 DateTime date = DateTime.Now; 4 return View(date); 5 }
View部分,添加視圖時,選擇Razor視圖引擎。
1 @{ 2 ViewBag.Title = "Index2"; 3 } 4 <h2>Index</h2> 5 The day is: @(((DateTime)Model).DayOfWeek)
上面的視圖是一個沒有類型或者說是弱類型的視圖,它不知道關於視圖模型對象的任何信息,而且將它做爲object對象的實例進行處理。爲了獲得DayOfWeek屬性的值,須要將object對象的實例強轉爲DateTime,這樣作可以實現效果,但卻讓視圖變得雜亂。
咱們能夠經過建立強類型的View來改進,即在View裏指定視圖模型對象的類型,只須要添加代碼:@model DateTime,以下所示:
1 @model DateTime 2 @{ 3 ViewBag.Title = "Index2"; 4 } 5 <h2>Index</h2> 6 The day is: @Model.DayOfWeek
運行結果以下:
能夠發現,使用強類型的視圖不只讓視圖變得整潔,並且方便咱們編碼,由於對屬性有智能感知。以下圖所示:
在前面咱們已經使用過了ViewBag視圖包這個特性,該特性容許你在一個動態對象上定義任意屬性,並可以在視圖裏面訪問。以下所示:
Controller部分
1 public ViewResult Index2() 2 { 3 ViewBag.Message = "Hello ViewBag!"; 4 ViewBag.Date=DateTime.Now; 5 return View(); 6 }
View 部分
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index2</title> 11 </head> 12 <body> 13 <div> 14 the message is:@ViewBag.message<br/> 15 the day is:@ViewBag.Date.DayOfWeek 16 </div> 17 </body> 18 </html>
運行效果以下:
相對於視圖模型對象方面,ViewBag有一個優勢,即它可以很容易地發送多個對象到視圖。假如咱們被限制只能使用視圖模型,那麼爲了實現相同的效果須要建立一個新的類型具備string和DateTime兩個類型的的成員。使用動態對象能夠在視圖中輸入屬性和方法調用的任意序列。
ViewData是在MVC3以前的版本中出現的,主要的功能相似於ViewBag,但ViewData是使用ViewDataDictionary類實現的而不是一個動態的類型,ViewDataDictionary類是一個常規的鍵/值對的集合,並經過Controller類的ViewData屬性訪問。以下示例:
Controller部分
1 public ViewResult Index2() 2 { 3 ViewData["message"] = "Hello ViewBag!"; 4 ViewData["Date"]=DateTime.Now; 5 return View(); 6 }
View部分
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index2</title> 11 </head> 12 <body> 13 <div> 14 the message is:@ViewData["message"]<br/> 15 the day is:@(((DateTime)ViewData["Date"]).DayOfWeek) 16 </div> 17 </body> 18 </html>
運行結果與VIewBag運行結果一致。
上面的代碼中,咱們看到ViewData須要對object對象進行類型轉換,如今有了ViewBag之後,推薦使用ViewBag,而且儘可能使用強類型視圖和視圖模型。
在【3.4.四、使用TempData保留重定向數據】節中會介紹使用TempData傳遞數據,這裏不做介紹。
有一種動做(Action)方法的一般結果並非直接產生輸出,而是把用戶的瀏覽器重定向導另外一個URL。大多數狀況下,這個URL是應用程序的另外一個動做(Action)方法,用來生成你但願用戶看到的輸出。
重定向的Action方法不產生任何的輸出,只是讓瀏覽器從新請求一個其它的URL。在MVC程序中,通常會定向到其它的Action方法來產生輸出。
在執行重定向時,發送了兩個HTTP代碼中的一個到瀏覽器。
(1)、發送HTTP 302狀態編碼,表明暫時重定向。(經常使用類型)
(2)、發送HTTP 301狀態編碼,表示永久重定向。(使用需謹慎)
重定向的類型有:1.重定向到文本URL 2.重定向到路由系統的URL 3.重定向到動做(Action)方法
對瀏覽器重定向最基本的方式是調用Redirect方法,它返回RedirectResult類的一個實例。以下示例:
若是但願重定的URL被表示成一個字符串,並做爲參數傳遞給Redirect方法。Redirect方法發送的是一個臨時重定向。能夠用RedirectPermanent方法發送一個永久重定向。例如:
使用重定向到文本URL的缺點是:限定了URL,當路由發生改變後,必須更新URL。幸虧咱們可使用路由系統,用RedirectToRoute方法來生成有效的URL該方法會建立 RedirectToRouteResult的一個實例。以下所示:
使用RedirectToAction方法可以很優雅的重定向到一個Action方法,這個方法僅僅是對RedirectToRoute方法的封裝,讓你指定Action方法和控制器的值,而不須要建立一個匿名類型。以下所示:
若是想重定向到其餘的控制器,須要提供一個控制器的名稱如:
注意:傳入的Action參數或控制器參數在它們被傳遞給路由系統以前是不會被驗證的,因此要確保目標控制器和Action方法是存在的。
重定向會引發瀏覽器提交整個新的HTTP請求,這意味着沒法訪問原始的請求的細節。若是想將數據從一個請求傳遞到下一個請求,可使用TempData()方法。
TempData相似於Session,不一樣的是TempData在被讀取之後會被標記刪除,而且當請求被處理的時候被移除。這是一個針對想在整個重定向過程當中保持短時間數據的很是完美的安排。以下示例:
首先添加一個MySecond控制器,並添加對應的視圖,視圖引擎爲Razor並編寫以下代碼:
MySecond控制器代碼
1 public class MySecondController : Controller 2 { 3 public ActionResult Index() 4 { 5 TempData["Message"] = "Hello TempData"; 6 TempData["Data"] = "TempData的值只能讀取一次"; 7 return View(); 8 } 9 public ActionResult TempDataTest() 10 { 11 return View(); 12 } 13 } 14
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 the Data is:@TempData["Data"] 15 </div> 16 </body> 17 </html>
TempDataTest視圖代碼
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>TempDataTest</title> 11 </head> 12 <body> 13 <div> 14 the Data is:@TempData["Data"]<br/> 15 the Message is:@TempData["Message"] 16 </div> 17 </body> 18 </html>
運行結果以下:
注意:在Index裏面沒有讀取TempData["Message"]的值,可是Index和TempDataTest都讀取了TempData["Data"]的值。這樣作是爲了驗證TempData裏面的值在不一樣的視圖只能讀取一次。
TempData裏面的值在一次請求裏面只能讀取一次。 若是咱們想讀取TempData的值可是又不讓它被刪除,可使用TempData.Peek(「Data」)方法。 若是想在保持一次TempData裏面的值,可使用TempData.Keep("Data")。
Content方法參數說明: 發送的文本數據、響應的HTTP content-type header、編碼格式。 能夠忽略最後兩個參數,在MVC框架假定數據是HTML(content type爲text/html)狀況下,它會查詢一種瀏覽器聲明支持的編碼格式。
1 using LinqService; 2 using System.Xml.Linq; 3 public ContentResult GetXMLData() 4 { 5 List<Books> books = bll.GetListBooks(); 6 XElement data = new XElement("BooksList", books.Select(e => 7 { 8 return new XElement("Books", 9 new XAttribute("title", e.Title), 10 new XAttribute("author", e.Author), 11 new XAttribute("Price", e.Price.ToString())); 12 })); 13 }
1 public ActionResult JSON() 2 { 3 JsonResult json = new JsonResult(); 4 5 UserInfo userinfo=new UserInfo{ ID=1, Name="張三", Age=11 }; 6 7 List<UserInfo> list=new List<UserInfo>{ 8 new UserInfo{ ID=1, Name="張三", Age=11 }, 9 new UserInfo{ ID=1, Name="張三", Age=11 }, 10 new UserInfo{ ID=1, Name="張三", Age=11 }, 11 new UserInfo{ ID=1, Name="張三", Age=11 } 12 }; 13 //json.Data = list; 14 //json.JsonRequestBehavior = JsonRequestBehavior.AllowGet; 15 //return json; 16 //return new JsonResult() { Data = list, JsonRequestBehavior=JsonRequestBehavior.AllowGet }; 17 18 return Json(userinfo,JsonRequestBehavior.AllowGet); 19 }
JSON是一個輕量級的基於文本格式,用來描述分層數據結構的, JSON是有效的javascript代碼,這意味着被全部的主流瀏覽器支持
在MVC框架裏面內置了JsonResult類,讓咱們可以序列化.NET對象爲json格式。經過Json方法能夠建立返回JsonResult結果類型
1 public FileResult AnnualReport() 2 { 3 string filename = @"D:\C#高級編程.doc"; 4 string contentType = "application/doc"; 5 string downloadName = "C#高級編程2013.doc"; 6 return File(filename, contentType, downloadName); 7 }
若是不知道具體的MIME類型,能夠指定contentType 爲application/octet-stream
lFileResult是一個抽象基類,其三個實現的子類 :
1.FilePathResult :直接把服務器的某個路徑下的文件發給瀏覽器
2.FileContentResult :發送一個內存中的二進制數據給瀏覽器
3.FileStreamResult:發送已經打開的文件流內容給瀏覽器。
發送特定的HTTP結果碼
可使用HttpStatusResult類將一個特定的HTTP狀態碼發送給瀏覽器。這個類沒有對應的控制器輔助器方法,所以必須直接對這個類進行實例化,以下所示:
1 public HttpStatusCodeResult StatusCode() 2 { 3 return new HttpStatusCodeResult(404,"file not found"); 4 }
發送404結果
可使用便利的HttpNotFoundResult類來獲取上面相同的效果,這個類派生於HttpStatusResult,並且能夠用控制器的便利方法HttpNotFound來建立。以下所示:
1 public HttpStatusCodeResult StatusCode() 2 { 3 return HttpNotFound(); 4 }
發送401結果
另外一個特定的HTTP狀態碼的封裝程序類是HttpUnauthorizeResult,它返回401代碼,用來指示一個未受權請求。以下所示:
1 public HttpStatusCodeResult StatusCode() 2 { 3 return new HttpUnauthorizedResult(); 4 }
總結:在本文章中,主要是對MVC設計模式中的控制器和動做方法作了詳細介紹,主要從實現自定義的控制器類兩種方式着手,而後介紹了控制器對數據的接收與響應系列內容。對數據接收主要來源查詢字符串值、表單數據、路由數據作了分別介紹,對數據的響應包括視圖 、文本數據、XML數據、JSON數據、文件或者二進制數據、返回錯誤和HTTP Codes、定製的ActionResult、重定向作了分別介紹。最後說明一下本文章的部分理論觀點引用自《Pro ASP.Net MVC 5.pdf》這本書。