[ASP.NET
MVC 小牛之路]09 - Controller 和 Action (1)
我們知道,在 MVC 中每個請求都會提交到 Controller 進行處理。Controller 是和請求密切相關的,它包含了對請求的邏輯處理,能對 Model 進行操作並選擇 View 呈現給使用者,對於業務和資料的邏輯代碼以及介面和輔助類庫等一般都不放到 Controller 中。
Controller 和 Action 的內容較多,我把它分成了兩篇,也可能會分成三篇。本篇介紹 Controller 的實現、Controller 對狀態資料的擷取、ActionResult 和 Action 的資料傳遞,後續將介紹 Controller 工廠、Action Invoker 和暫時還沒想好或正在學習的一些較進階的特性。
本文目錄
繼承 IController 介面
在本系列前面的文章中,我們添加的 Controller 都是一個繼承自抽象類別 System.Web.Mvc.Controller 的普通類(請注意:controller(或Controller) 和 Controller 類在本文是兩個意思,請在閱讀本文時根據上下文理解)。Controller 抽象類別封裝了很多很實用的功能,讓開發人員不用自己去寫那些重複煩瑣的處理代碼。
如果不使用封裝的 Controller 抽象類別,我們也可以通過實現 IController 介面來建立自己的 controller。IController 介面中只有一個 Exctute 方法:
public interface IController { void Execute(RequestContext requestContext); }
Controller 介面在 System.Web.Mvc 命名空間下,一個結構非常簡單的介面。
當請求送到一個實現了 IController 介面的 controller 類時(路由系統通過請求的URL能夠找到controller),Execute 方法被調用。
下面我們建立一個空的 MVC 應用程式,在 Controllers 檔案夾下添加一個實現了 IController 的類,並做一些簡單的操作,如下:
using System.Web.Mvc;using System.Web.Routing;namespace MvcApplication1.Controllers { public class BasicController : IController { public void Execute(RequestContext requestContext) { string controller = (string)requestContext.RouteData.Values["controller"]; string action = (string)requestContext.RouteData.Values["action"]; requestContext.HttpContext.Response.Write( string.Format("Controller: {0}, Action: {1}", controller, action)); } }}
運行應用程式,URL 定位到 /Basic/Index(你可以把 Index 改成其他任意片段名稱),結果如下:
實現了 IController 的類,MVC就會辨識它為一個 controller 類,根據 controller 名把對應的請求交給這個類處理。
但對於一個稍稍複雜的應用程式,自己實現 IController 介面是要做很多工作的,我們很少會這麼做。通過這我們更好地理解了 controller 的運行,controller 中一切對請求的處理都是從 Execute 方法開始的。
繼承 Controller 抽象類別
MVC 允許我們自由地進行自訂和擴充,比如像上面講的你可以實現 IController 介面來建立對各類請求的各種處理並產生結果。不喜歡 Action 方法或不關心 View,那麼你可以自己動手寫一個更好更快更優雅的 controller 來處理請求。但像前面說的,自己實現 IController 介面要做很多工作,最重要的是沒有經過長期實踐測試,代碼的健壯性得不到保證, 一般不建議你這麼做。MVC 架構的 System.Web.Mvc.Controller 類,提供了足夠實用的特性來方便我們對請求的處理和返回結果。
繼承 Controller 類的 controller 我們已經使用過很多次了,對它已經有一定的瞭解,它提供了如下幾個關鍵的特性:
Action方法:一個 Controller,它的行為被分為多個方法,通常一個方法對應著一個請求,並且可以通過方法參數來取得請求傳遞過來的資料。
ActionResult:可以返回一個描述了 Action 方法執行結果的對象,這樣的好處是想返回什麼結果就指定對應的返回對象就行,不用關心怎麼去執行並產生結果。
Filters:通過C#特性,對某一種行為的處理(比如授權和驗證)進行封裝,方便了在多個 Controller 和 Action 方法之間進行重用。
所以,如果你不是因為特殊的需求或閑得蛋疼,建立一個滿足要求的 Controller 最好的途徑是繼承 Controller 抽象類別。由於之前我們已經使用過多次,這裡就不再進行具體的示範了,但我們還是來看一下它的代碼結構。
在 Controllers 檔案夾下添加一個 Controller,VS 已經把類的結構幫我們產生好了,如果你喜歡整潔,會習慣性地把不需要用到的引用刪掉,代碼如下:
using System.Web.Mvc;namespace MvcApplication1.Controllers { public class DerivedController : Controller { public ActionResult Index() { ViewBag.Message = "Hello from the DerivedController Index method"; return View("MyView"); } }}
我們可以查看 Controller 抽象類別的定義,發現它是繼承 ControllerBase 類的,在 ControllerBase 類中實現了 IController 介面的 Execute 方法,這個方法是MVC對請求進行處理的各個組件的入口,其中包括通過路由系統找到 Action 方法並調用。
Controller 類內部使用 Razor 視圖系統來呈現 View,這裡通過 View 方法,指定 View 的名稱參數來告訴 MVC 選擇 MyView 視圖來返回給使用者結果。
在 Controller 中擷取狀態資料
我們經常需要訪問用戶端提交過來的資料,比如 QueryString 值、表單值和通過路由系統來自 URL 的參數值,這些值都可稱為狀態資料。下面是 Controller 中擷取狀態資料的三個主要來源:
一系列的內容物件。
傳遞給 Action 方法的參數。
顯式的調用架構的模型繫結(Model Binding)特性。
從內容物件中擷取狀態資料
擷取狀態資料最直接的方法就是從內容物件中提取。當你建立了一個繼承自 Controller 類的 Controller 時,可以通過一系列的屬性可以方便的訪問到和請求相關的資料,這些屬性包括 Request、Response、RouteData、HttpContext 和 Server,每一個都提供了請求相關的不同類型的資訊。下面列出了最常的內容物件:
在 Action 方法中可以使用任意內容物件來擷取請求相關的資訊,如下面在 Action 方法中所示範的:
...public ActionResult RenameProduct() { //訪問不同的內容物件 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product"); //從POST請求提交的表單中擷取資料 string oldProductName = Request.Form["OldName"]; string newProductName = Request.Form["NewName"]; bool result = AttemptProductRename(oldProductName, newProductName); ViewData["RenameResult"] = result; return View("ProductRenamed");}...
這些上下對象不用特意去記,用的時候,你可以通過VS的智能提示來瞭解這些內容物件。
使用 Action 方法參數擷取狀態資料
在本系列的前面的文章中,我們已經知識如何通過 Action 參數來接收資料,這種方法和上面的從內容物件中擷取相比,它更為簡潔明了。比如,我們有下面這樣一個使用內容物件的 Action 方法:
public ActionResult ShowWeatherForecast() { string city = (string)RouteData.Values["city"]; DateTime forDate = DateTime.Parse(Request.Form["forDate"]); // do something ... return View();}
我們可以像下面這樣使用 Action 方法參數來重寫它:
public ActionResult ShowWeatherForecast(string city, DateTime forDate) { // do something ... return View(); }
它不僅易讀性強,也方便進行單元測試。
Action 方法的參數不允許使用 ref 和 out 參數,這是沒有意義的。
MVC 架構通過檢查內容物件來為 Action 方法的參數提供值,它的名稱是不區分大小寫,比如 Action 方法的 city 參數的值可以是通過 Request.Form["City"] 來擷取的。
理解 Action 方法的參數是如何被賦值的
Controller 類通過 MVC 架構的 value provider 和 model binder 組件來為 Action 方法擷取參數的值。
value provider 提供了一系列Controller中可以訪問到的值,在內部它通過從 Request.Form、Request.QueryString、Request.Files 和 RouteData.Values 等內容物件中提取資料(索引值集合),然後把資料傳遞給 model binder,model binder 試圖將這些資料與Action方法的參數進行匹配。預設的 model binder 可以建立和賦值給任何.NET類型對象參數(即 Action 方法的參數),包括集合和自訂的類型。
在這不對 model binder 進行介紹,我將在本系列的後續博文中對其進行專門的介紹。
理解 ActionResult
ActionResult 是描述 Action 方法執行結果的對象,它的好處是想返回什麼結果就指定對應的返回對象就行,不用關心如何使用Response對象來組織和產生結果。ActionResult 是一個命令模式的例子,這種模式通過儲存和傳遞對象來描述操作。
當 MVC 架構從 Action 方法中接收到一個 ActionResult 對象,它調用這個對象的 ExecuteResult 方法,其內部是通過 Response 對象來返回我們想要的輸出結果。
為了更好的理解,我們通過繼承 ActionResult 類來自訂一個 ActionResult。在MVC工程中添加一個Infrastructure檔案夾,在裡面建立一個名為 CustomRedirectResult 的類檔案,代碼如下:
using System.Web.Mvc;namespace MvcApplication1.Infrastructure { public class CustomRedirectResult : ActionResult { public string Url { get; set; } public override void ExecuteResult(ControllerContext context) { string fullUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); context.HttpContext.Response.Redirect(fullUrl); } }}
當我們建立一個 CustomRedirectResult 類的執行個體時,我們可以傳遞想要跳轉的 URL。當 Action 方法執行結束時,MVC 架構調用 ExecuteResult 方法,ExecuteResult 方法通過 ControllerContext 對象獲得 Response 對象,然後調用 Redirect 方法。
下面我們在 Controller 中使用自訂的 CustomRedirectResult:
public class DerivedController : Controller { ... public ActionResult ProduceOutput() { if (Server.MachineName == "WL-PC") { return new CustomRedirectResult { Url = "/Basic/Index" }; } else { Response.Write("Controller: Derived, Action: ProduceOutput"); return null; } }
運行後我們看到如下結果:
當運行在本機(WL-PC)時直接重新導向到了指定的/Basic/Index。
上面我們通過自訂 CustomRedirectResult 來實現重新導向,我們可以用 MVC 架構提供的方法,如下:
... public ActionResult ProduceOutput() { return new RedirectResult("/Basic/Index"); }
為了使用方便,Controller 類中為大部分類型的 ActionResult 提供簡便的方法,如上面的可像下面這樣簡寫:
... public ActionResult ProduceOutput() { return Redirect("/Basic/Index"); }
MVC架構套件含了許多 ActionResult 類型,這些類型都繼承自 ActionResult 類,大部分在 Controller 類中都有簡便的方法,下面列舉了一些:
除了該表列出來的,還有ContentResult、FileResult、JsonResult 和 JavaScriptResult。具體每種ActionResult類型的用法這裡就不講了,大家可以看看蔣老師的瞭解ASP.NET
MVC幾種ActionResult的本質系列的文章。
幾種從 Action 傳遞資料到 View 的方式
我們經常需要在 Action 方法中傳遞資料到一個 View 中,MVC 架構為此提供了一些很方便的操作。下面簡單簡介幾種常用的方式。
View Model 對象
通過 View Model 對象傳遞資料給View,這是最常用的一種,在 Acton 方法執行結束時通過 View 方法傳遞 View Model 對象給 View,如下代碼所示:
... public ViewResult Index() { DateTime date = DateTime.Now; return View(date); }
在 View 中我們通過 Model 屬性來使用傳遞過來的 View Model 對象,如下:
@model DateTime @{ ViewBag.Title = "Index"; }<h2>Index</h2> The day is: @Model.DayOfWeek
在 Razor 視圖引擎中,@model 的作用是聲明 odel 屬性的類型,省去了類型轉換的麻煩,而 @Model 是V iew Model 對象的引用。
ViewBag、ViewData 和 TempData 屬性
ViewBag、ViewData 和 TempData 都是 Controller 和 View 中能訪問到的屬性,都是用來儲存小量的資料,他們的區別如下:
ViewBag,是一個動態(dynamic)的弱類型,在程式啟動並執行時候解析,是 MVC3 中新增的特性,只在當前View有效。
ViewData,是一個字典集合,也是只在當前View有效,效能比 ViewBag 高,但是使用的時候需要類型轉換。
TempData,也是字典集合,一般用於兩個請求之間臨時緩衝內容或頁面間傳遞訊息,儲存在 Session 中,使用完以後則從 Session 中被清除。
下面是三者使用的例子,先在 Controller 中分別用三者儲存小資料:
public class DerivedController : Controller { public ActionResult Index() { ViewBag.DayOfWeek = DateTime.Now.DayOfWeek; ViewData["DayOfMonth"] = DateTime.Now.Day; return View(); } public ActionResult ProduceOutput() { TempData["Message"] = "Warning message from Derived Controller."; return Redirect("/Home/Index"); }}
在 Views/Derived 目錄下的 Index.cshtml 中,取出 ViewBag 和 ViewData 中的儲存的資料:
...Day of week from ViewBag: @ViewBag.DayOfWeek<p /> Day of month from ViewData: @ViewData["DayOfMonth"]
在 Views/Home 目錄下的 Index.cshtml 中,取 TempData 中的資料如下:
...@TempData["Message"]
當請求 /Derived/ProduceOutput 時,ProduceOutput 方法將一條訊息存到 TempData 中,並跳轉到 /Home/Index。
下面是分別是將URL定位到 /Derived/Index 和 /Derived/ProduceOutput 時的結果:
一般在當前 View 中使用 ViewBag 或 ViewData,在兩個請求之間傳遞臨時資料用 TempData。由於 TempData 被使用後即被釋放,所以如果要二次使用 TempData 中的資料就需要將其存到其他變數中。
以上就是[ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!