我們知道,在 MVC 中每個(gè)請(qǐng)求都會(huì)提交到 Controller 進(jìn)行處理。Controller 是和請(qǐng)求密切相關(guān)的,它包含了對(duì)請(qǐng)求的邏輯處理,能對(duì) Model 進(jìn)行操作并選擇 View 呈現(xiàn)給用戶,對(duì)于業(yè)務(wù)和數(shù)據(jù)的邏輯代碼以及接口和輔助類庫(kù)等一般都不放到 Controller 中。
Controller 和 Action 的內(nèi)容較多,我把它分成了兩篇,也可能會(huì)分成三篇。本篇介紹 Controller 的實(shí)現(xiàn)、Controller 對(duì)狀態(tài)數(shù)據(jù)的獲取、ActionResult 和 Action 的數(shù)據(jù)傳遞,后續(xù)將介紹 Controller 工廠、Action Invoker 和暫時(shí)還沒(méi)想好或正在學(xué)習(xí)的一些較高級(jí)的特性。
本文目錄
在本系列前面的文章中,我們添加的 Controller 都是一個(gè)繼承自抽象類 System.Web.Mvc.Controller 的普通類(請(qǐng)注意:controller(或Controller) 和 Controller 類在本文是兩個(gè)意思,請(qǐng)?jiān)陂喿x本文時(shí)根據(jù)上下文理解)。Controller 抽象類封裝了很多很實(shí)用的功能,讓開(kāi)發(fā)人員不用自己去寫(xiě)那些重復(fù)煩瑣的處理代碼。
如果不使用封裝的 Controller 抽象類,我們也可以通過(guò)實(shí)現(xiàn) IController 接口來(lái)創(chuàng)建自己的 controller。IController 接口中只有一個(gè) Exctute 方法:
public interface IController { void Execute(RequestContext requestContext); }
IController 接口在 System.Web.Mvc 命名空間下,一個(gè)結(jié)構(gòu)非常簡(jiǎn)單的接口。
當(dāng)請(qǐng)求送到一個(gè)實(shí)現(xiàn)了 IController 接口的 controller 類時(shí)(路由系統(tǒng)通過(guò)請(qǐng)求的URL能夠找到controller),Execute 方法被調(diào)用。
下面我們創(chuàng)建一個(gè)空的 MVC 應(yīng)用程序,在 Controllers 文件夾下添加一個(gè)實(shí)現(xiàn)了 IController 的類,并做一些簡(jiǎn)單的操作,如下:
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)); } }}
運(yùn)行應(yīng)用程序,URL 定位到 /Basic/Index(你可以把 Index 改成其他任意片段名稱),結(jié)果如下:
實(shí)現(xiàn)了 IController 的類,MVC就會(huì)辨識(shí)它為一個(gè) controller 類,根據(jù) controller 名把對(duì)應(yīng)的請(qǐng)求交給這個(gè)類處理。
但對(duì)于一個(gè)稍稍復(fù)雜的應(yīng)用程序,自己實(shí)現(xiàn) IController 接口是要做很多工作的,我們很少會(huì)這么做。通過(guò)這我們更好地理解了 controller 的運(yùn)行,controller 中一切對(duì)請(qǐng)求的處理都是從 Execute 方法開(kāi)始的。
MVC 允許我們自由地進(jìn)行自定義和擴(kuò)展,比如像上面講的你可以實(shí)現(xiàn) IController 接口來(lái)創(chuàng)建對(duì)各類請(qǐng)求的各種處理并生成結(jié)果。不喜歡 Action 方法或不關(guān)心 View,那么你可以自己動(dòng)手寫(xiě)一個(gè)更好更快更優(yōu)雅的 controller 來(lái)處理請(qǐng)求。但像前面說(shuō)的,自己實(shí)現(xiàn) IController 接口要做很多工作,最重要的是沒(méi)有經(jīng)過(guò)長(zhǎng)期實(shí)踐測(cè)試,代碼的健壯性得不到保證, 一般不建議你這么做。MVC 框架的 System.Web.Mvc.Controller 類,提供了足夠?qū)嵱玫奶匦詠?lái)方便我們對(duì)請(qǐng)求的處理和返回結(jié)果。
繼承 Controller 類的 controller 我們已經(jīng)使用過(guò)很多次了,對(duì)它已經(jīng)有一定的了解,它提供了如下幾個(gè)關(guān)鍵的特性:
所以,如果你不是因?yàn)樘厥獾男枨蠡蜷e得蛋疼,創(chuàng)建一個(gè)滿足要求的 Controller 最好的途徑是繼承 Controller 抽象類。由于之前我們已經(jīng)使用過(guò)多次,這里就不再進(jìn)行具體的演示了,但我們還是來(lái)看一下它的代碼結(jié)構(gòu)。
在 Controllers 文件夾下添加一個(gè) Controller,VS 已經(jīng)把類的結(jié)構(gòu)幫我們生成好了,如果你喜歡整潔,會(huì)習(xí)慣性地把不需要用到的引用刪掉,代碼如下:
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 抽象類的定義,發(fā)現(xiàn)它是繼承 ControllerBase 類的,在 ControllerBase 類中實(shí)現(xiàn)了 IController 接口的 Execute 方法,這個(gè)方法是MVC對(duì)請(qǐng)求進(jìn)行處理的各個(gè)組件的入口,其中包括通過(guò)路由系統(tǒng)找到 Action 方法并調(diào)用。
Controller 類內(nèi)部使用 Razor 視圖系統(tǒng)來(lái)呈現(xiàn) View,這里通過(guò) View 方法,指定 View 的名稱參數(shù)來(lái)告訴 MVC 選擇 MyView 視圖來(lái)返回給用戶結(jié)果。
我們經(jīng)常需要訪問(wèn)客戶端提交過(guò)來(lái)的數(shù)據(jù),比如 QueryString 值、表單值和通過(guò)路由系統(tǒng)來(lái)自 URL 的參數(shù)值,這些值都可稱為狀態(tài)數(shù)據(jù)。下面是 Controller 中獲取狀態(tài)數(shù)據(jù)的三個(gè)主要來(lái)源:
獲取狀態(tài)數(shù)據(jù)最直接的方法就是從上下文對(duì)象中提取。當(dāng)你創(chuàng)建了一個(gè)繼承自 Controller 類的 Controller 時(shí),可以通過(guò)一系列的屬性可以方便的訪問(wèn)到和請(qǐng)求相關(guān)的數(shù)據(jù),這些屬性包括 Request、Response、RouteData、HttpContext 和 Server,每一個(gè)都提供了請(qǐng)求相關(guān)的不同類型的信息。下面列出了最常的上下文對(duì)象:
在 Action 方法中可以使用任意上下文對(duì)象來(lái)獲取請(qǐng)求相關(guān)的信息,如下面在 Action 方法中所演示的:
...public ActionResult RenameProduct() { //訪問(wèn)不同的上下文對(duì)象 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請(qǐng)求提交的表單中獲取數(shù)據(jù) string oldProductName = Request.Form["OldName"]; string newProductName = Request.Form["NewName"]; bool result = AttemptProductRename(oldProductName, newProductName); ViewData["RenameResult"] = result; return View("ProductRenamed");}...
這些上下對(duì)象不用特意去記,用的時(shí)候,你可以通過(guò)VS的智能提示來(lái)了解這些上下文對(duì)象。
在本系列的前面的文章中,我們已經(jīng)知識(shí)如何通過(guò) Action 參數(shù)來(lái)接收數(shù)據(jù),這種方法和上面的從上下文對(duì)象中獲取相比,它更為簡(jiǎn)潔明了。比如,我們有下面這樣一個(gè)使用上下文對(duì)象的 Action 方法:
public ActionResult ShowWeatherForecast() { string city = (string)RouteData.Values["city"]; DateTime forDate = DateTime.Parse(Request.Form["forDate"]); // do something ... return View();}
我們可以像下面這樣使用 Action 方法參數(shù)來(lái)重寫(xiě)它:
public ActionResult ShowWeatherForecast(string city, DateTime forDate) { // do something ... return View(); }
它不僅易讀性強(qiáng),也方便進(jìn)行單元測(cè)試。
Action 方法的參數(shù)不允許使用 ref 和 out 參數(shù),這是沒(méi)有意義的。
MVC 框架通過(guò)檢查上下文對(duì)象來(lái)為 Action 方法的參數(shù)提供值,它的名稱是不區(qū)分大小寫(xiě)的,比如 Action 方法的 city 參數(shù)的值可以是通過(guò) Request.Form["City"] 來(lái)獲取的。
Controller 類通過(guò) MVC 框架的 value provider 和 model binder 組件來(lái)為 Action 方法獲取參數(shù)的值。
value provider 提供了一系列Controller中可以訪問(wèn)到的值,在內(nèi)部它通過(guò)從 Request.Form、Request.QueryString、Request.Files 和 RouteData.Values 等上下文對(duì)象中提取數(shù)據(jù)(鍵值集合),然后把數(shù)據(jù)傳遞給 model binder,model binder 試圖將這些數(shù)據(jù)與Action方法的參數(shù)進(jìn)行匹配。默認(rèn)的 model binder 可以創(chuàng)建和賦值給任何.NET類型對(duì)象參數(shù)(即 Action 方法的參數(shù)),包括集合和自定義的類型。
在這不對(duì) model binder 進(jìn)行介紹,我將在本系列的后續(xù)博文中對(duì)其進(jìn)行專門(mén)的介紹。
ActionResult 是描述 Action 方法執(zhí)行結(jié)果的對(duì)象,它的好處是想返回什么結(jié)果就指定對(duì)應(yīng)的返回對(duì)象就行,不用關(guān)心如何使用Response對(duì)象來(lái)組織和生成結(jié)果。ActionResult 是一個(gè)命令模式的例子,這種模式通過(guò)存儲(chǔ)和傳遞對(duì)象來(lái)描述操作。
當(dāng) MVC 框架從 Action 方法中接收到一個(gè) ActionResult 對(duì)象,它調(diào)用這個(gè)對(duì)象的 ExecuteResult 方法,其內(nèi)部是通過(guò) Response 對(duì)象來(lái)返回我們想要的輸出結(jié)果。
為了更好的理解,我們通過(guò)繼承 ActionResult 類來(lái)自定義一個(gè) ActionResult。在MVC工程中添加一個(gè)Infrastructure文件夾,在里面創(chuàng)建一個(gè)名為 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); } }}
當(dāng)我們創(chuàng)建一個(gè) CustomRedirectResult 類的實(shí)例時(shí),我們可以傳遞想要跳轉(zhuǎn)的 URL。當(dāng) Action 方法執(zhí)行結(jié)束時(shí),MVC 框架調(diào)用 ExecuteResult 方法,ExecuteResult 方法通過(guò) ControllerContext 對(duì)象獲得 Response 對(duì)象,然后調(diào)用 Redirect 方法。
下面我們?cè)?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; } }}
運(yùn)行后我們看到如下結(jié)果:
當(dāng)運(yùn)行在本機(jī)(WL-PC)時(shí)直接重定向到了指定的/Basic/Index。
上面我們通過(guò)自定義 CustomRedirectResult 來(lái)實(shí)現(xiàn)重定向,我們可以用 MVC 框架提供的方法,如下:
... public ActionResult ProduceOutput() { return new RedirectResult("/Basic/Index"); }
為了使用方便,Controller 類中為大部分類型的 ActionResult 提供簡(jiǎn)便的方法,如上面的可像下面這樣簡(jiǎn)寫(xiě):
... public ActionResult ProduceOutput() { return Redirect("/Basic/Index"); }
MVC框架包含了許多 ActionResult 類型,這些類型都繼承自 ActionResult 類,大部分在 Controller 類中都有簡(jiǎn)便的方法,下面列舉了一些:
除了該表列出來(lái)的,還有ContentResult、FileResult、JsonResult 和 JavaScriptResult。具體每種ActionResult類型的用法這里就不講了,大家可以看看蔣老師的了解ASP.NET MVC幾種ActionResult的本質(zhì)系列的文章。
我們經(jīng)常需要在 Action 方法中傳遞數(shù)據(jù)到一個(gè) View 中,MVC 框架為此提供了一些很方便的操作。下面簡(jiǎn)單簡(jiǎn)介幾種常用的方式。
通過(guò) View Model 對(duì)象傳遞數(shù)據(jù)給View,這是最常用的一種,在 Acton 方法執(zhí)行結(jié)束時(shí)通過(guò) View 方法傳遞 View Model 對(duì)象給 View,如下代碼所示:
... public ViewResult Index() { DateTime date = DateTime.Now; return View(date); }
在 View 中我們通過(guò) Model 屬性來(lái)使用傳遞過(guò)來(lái)的 View Model 對(duì)象,如下:
@model DateTime @{ ViewBag.Title = "Index"; }<h2>Index</h2> The day is: @Model.DayOfWeek
在 Razor 視圖引擎中,@model 的作用是聲明 odel 屬性的類型,省去了類型轉(zhuǎn)換的麻煩,而 @Model 是V iew Model 對(duì)象的引用。
ViewBag、ViewData 和 TempData 都是 Controller 和 View 中能訪問(wèn)到的屬性,都是用來(lái)存儲(chǔ)小量的數(shù)據(jù),他們的區(qū)別如下:
下面是三者使用的例子,先在 Controller 中分別用三者存儲(chǔ)小數(shù)據(jù):
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 中的存儲(chǔ)的數(shù)據(jù):
...Day of week from ViewBag: @ViewBag.DayOfWeek<p /> Day of month from ViewData: @ViewData["DayOfMonth"]
在 Views/Home 目錄下的 Index.cshtml 中,取 TempData 中的數(shù)據(jù)如下:
...
@TempData["Message"]
當(dāng)請(qǐng)求 /Derived/ProduceOutput 時(shí),ProduceOutput 方法將一條消息存到 TempData 中,并跳轉(zhuǎn)到 /Home/Index。
下面是分別是將URL定位到 /Derived/Index 和 /Derived/ProduceOutput 時(shí)的結(jié)果:
一般在當(dāng)前 View 中使用 ViewBag 或 ViewData,在兩個(gè)請(qǐng)求之間傳遞臨時(shí)數(shù)據(jù)用 TempData。由于 TempData 被使用后即被釋放,所以如果要二次使用 TempData 中的數(shù)據(jù)就需要將其存到其他變量中。
聯(lián)系客服