天天看點

(三) Controller/Action 深入解析與應用執行個體(mvc)

轉自: http://www.cnblogs.com/zhangziqiu/archive/2009/03/11/Aspnet-MVC-3.html

一.摘要

一個Url請求經過了Routing處理後會調用Controller的Action方法. 中間的過程是怎樣的? Action方法中傳回ActionResult對象後,如何到達View的? 本文将講解Controller的基本用法,  深入分析Controller的運作機制, 并且提供了建立所有類型Action的代碼. 值得學習ASP.NET MVC時參考.

二.承上啟下

在上一篇文章中, 我已經學會了如何使用Routing擷取Controller和Action, 随後的程式會調用Controller中的Action方法.

每個Action方法都要傳回一個ActionResult對象. 一個Action會将資料傳遞給View,如圖:

(三) Controller/Action 深入解析與應用執行個體(mvc)

三.Controller與Action的作用

1.職責

Controller負責将擷取Model資料并将Model傳遞給View對象.通知View對象顯示.

2.ASP.NET MVC中的Controller和Action

在ASP.NET MVC中, 一個Controller可以包含多個Action. 每一個Action都是一個方法, 傳回一個ActionResult執行個體.

ActionResult類包括ExecuteResult方法, 當ActionResult對象傳回後會執行此方法.

下面分層次的總結Controller 處理流程:

1. 頁面處理流程

發送請求 –> UrlRoutingModule捕獲請求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()

2.MvcHandler.ProcessRequest() 處理流程:

使用工廠方法擷取具體的Controller –> Controller.Execute() –> 釋放Controller對象

3.Controller.Execute() 處理流程

擷取Action –> 調用Action方法擷取傳回的ActionResult –> 調用ActionResult.ExecuteResult() 方法

4.ActionResult.ExecuteResult() 處理流程

擷取IView對象-> 根據IView對象中的頁面路徑擷取Page類-> 調用IView.RenderView() 方法(内部調用Page.RenderView方法)

通過對MVC源代碼的分析,我們了解到Controller對象的職責是傳遞資料,擷取View對象(實作了IView接口的類),通知View對象顯示.

View對象的作用是顯示.雖然顯示的方法RenderView()是由Controller調用的,但是Controller僅僅是一個"指揮官"的作用, 具體的顯示邏輯仍然在View對象中.

需要注意IView接口與具體的ViewPage之間的聯系.在Controller和View之間還存在着IView對象.對于ASP.NET程式提供了WebFormView對象實作了IView接口.WebFormView負責根據虛拟目錄擷取具體的Page類,然後調用Page.RenderView().

四.ActionResult解析

通過上面的流程,我們知道了ActionResult對象在整個流程中的作用.ActionResult是一個抽象類, 在Action中傳回的都是其派生類.下面是我整理的ASP.NET MVC 1.0 版本中提供的ActionResult派生類:

類名 抽象類 父類 功能
ContentResult 根據内容的類型和編碼,資料内容.
EmptyResult 空方法.
FileResult abstract 寫入檔案内容,具體的寫入方式在派生類中.
FileContentResult 通過 檔案byte[] 寫入檔案.
FilePathResult 通過 檔案路徑 寫入檔案.
FileStreamResult 通過 檔案Stream 寫入檔案.
HttpUnauthorizedResult 抛出401錯誤
JavaScriptResult 傳回javascript檔案
JsonResult 傳回Json格式的資料
RedirectResult 使用Response.Redirect重定向頁面
RedirectToRouteResult 根據Route規則重定向頁面
ViewResultBase 調用IView.Render()
PartialViewResult

調用父類ViewResultBase 的ExecuteResult方法.

重寫了父類的FindView方法.

尋找使用者控件.ascx檔案

ViewResult 尋找頁面.aspx檔案

目前ASP.NET MVC還沒有提供官方的ActionResult清單.上面的清單是我在源代碼中分析得出的.有些解釋的可能不夠清楚,請諒解.

下面我将列舉各個ActionResult的執行個體.

五.執行個體應用

1.添加Controller

安裝了ASP.NET MVC後, 在項目上點選右鍵會找到添加Controller項:

(三) Controller/Action 深入解析與應用執行個體(mvc)

2.添加Action

下面這個類提供了傳回各種類型的ActionResult的Action執行個體:

public class DemoController : Controller { /// <summary> /// http://localhost:1847/Demo/ContentResultDemo /// </summary> /// <returns></returns> public ActionResult ContentResultDemo() { string contentString = "ContextResultDemo!"; return Content(contentString); } /// <summary> /// http://localhost:1847/Demo/EmptyResultDemo /// </summary> /// <returns></returns> public ActionResult EmptyResultDemo() { return new EmptyResult(); } /// <summary> /// http://localhost:1847/Demo/FileContentResultDemo /// </summary> /// <returns></returns> public ActionResult FileContentResultDemo() { FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read); byte[] buffer = new byte[Convert.ToInt32(fs.Length)]; fs.Read(buffer, 0, Convert.ToInt32(fs.Length) ); return              @"image/gif"); } /// <summary> /// http://localhost:1847/Demo/FilePathResultDemo /// </summary> /// <returns></returns> public ActionResult FilePathResultDemo() { //可以将一個jpg格式的圖像輸出為gif格式 return File(Server.MapPath(@"/resource/Images/2.jpg"), @"image/gif"); } /// <summary> /// http://localhost:1847/Demo/FileStreamResultDemo /// </summary> /// <returns></returns> public ActionResult FileStreamResultDemo() { FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read); return File(fs, @"image/gif"); } /// <summary> /// http://localhost:1847/Demo/HttpUnauthorizedResultDemo /// </summary> /// <returns></returns> public ActionResult HttpUnauthorizedResultDemo() { return new HttpUnauthorizedResult(); } /// <summary> /// http://localhost:1847/Demo/JavaScriptResultDemo /// </summary> /// <returns></returns> public ActionResult JavaScriptResultDemo() { return JavaScript(@"alert(""Test JavaScriptResultDemo!"")"); } /// <summary> /// http://localhost:1847/Demo/JsonResultDemo /// </summary> /// <returns></returns> public ActionResult JsonResultDemo() { var tempObj = new { Controller = "DemoController", Action = "JsonResultDemo" }; return Json(tempObj); } /// <summary> /// http://localhost:1847/Demo/RedirectResultDemo /// </summary> /// <returns></returns> public ActionResult RedirectResultDemo() { return Redirect(@"http://localhost:1847/Demo/ContentResultDemo"); } /// <summary> /// http://localhost:1847/Demo/RedirectToRouteResultDemo /// </summary> /// <returns></returns> public ActionResult RedirectToRouteResultDemo() { return RedirectToAction(@"FileStreamResultDemo"); } /// <summary> /// http://localhost:1847/Demo/PartialViewResultDemo /// </summary> /// <returns></returns> public ActionResult PartialViewResultDemo() { return PartialView(); } /// <summary> /// http://localhost:1847/Demo/RedirectToRouteResultDemo /// </summary> /// <returns></returns> public ActionResult ViewResultDemo() { //如果沒有傳入View名稱, 預設尋找與Action名稱相同的View頁面. return View(); } }      

在文章最後提供有完整執行個體代碼下載下傳.

六.Controller 深入分析

在研究Controller/Action的流程過程中, 發現了ASP.NET MVC一些問題.

1.Routing元件與MVC架構的結合

Routing元件和ASP.NET MVC并不是一個項目, 在ASP.NET MVC中僅僅是使用了Routing元件, 在源代碼中是通過dll的方式引用的.Routing元件已經包含在.net framework 3.5 sp1中了.

那麼ASP.NET MVC是如何應用Routing元件的呢?

Routing元件擷取了Url中的資料後, 會将資料儲存在一個 RouteData 對象中.并将請求傳遞給一個實作了IRouteHandler接口的對象. 在Asp.net MVC中提供的MvcRouteHandler類實作了此接口, Routing 将請求傳遞給MvcRouteHandler的GetHttpHandler方法.下面是源代碼:

IRouteHandler接口:

public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); }      

MvcRouteHandler類:

public class MvcRouteHandler : IRouteHandler { protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); } #region IRouteHandler Members IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return GetHttpHandler(requestContext); } #endregion }      

曾經我認為IRouteHandler是多餘的, 用IHttpHandler就夠了. 現在知道了為何要定義這個接口. 主要是為了傳遞RouteData對象.GetHttpHandler方法需要一個RequestContext 對象.RequestContext 是 System.Web.Routing程式集中的類, 裡面除了處理請求需要的HttpContextBase對象,還包括了一個RouteData對象.

RequestContext類:

public class RequestContext { public RequestContext(HttpContextBase httpContext, RouteData routeData); public HttpContextBase HttpContext { get; } public RouteData RouteData { get; } }      

Routing元件在Web.Config中注冊了一個HttpModule: System.Web.Routing.UrlRoutingModule, 而不是HttpHandler:

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>      

可惜看不到這個類的源代碼. 所有請求最後都是要傳遞給IHttpHandler對象處理, 主要的工作是編譯頁面, 是以我猜測這個Module将請求截獲後通過IRouteHandler接口對象擷取一個HttpHandler, 然後将處理移交給擷取到的HttpHandler.

ASP.NET MVC 中實作了IHttpHandler接口的類是MvcHandler, MvcRouteHandler.GetHttpHandler方法就是傳回一個MvcHandler對象. MvcHandler類的構造函數需要傳入一個RequestContext對象. 實作的IHttpHandler接口方法處理過程中都需要依賴這個對象.

但是微軟在這裡的處理有一些不足. MvcHandler雖然實作了IHttpHandler接口但是不能被當作IHttpHandler接口使用. 因為IHttpHandler中沒有定義RequestContext屬性, 如果一個MvcHandler對象此屬性沒有指派則會出錯, 也沒有将預設的無參數構造函數設定為private, 是以理論上可以很随意的執行個體化一個MvcHandler而不為其RequestContext屬性指派.

IRouteHandler想實作的語意是: 傳回一個具有RequestContext屬性的IHttpHandler對象.

但是最後的實作結果是: 提供"傳回IHttpHandler對象"的方法,  此方法接收RequestContext對象參數.

還需要注意ControllerContext類. 在Controller的處理過程中使用此對象作為儲存上下文資料的容器.下面是這幾個類的包含關系:

(三) Controller/Action 深入解析與應用執行個體(mvc)

可以看到在ControllerContext中包含了RequestContext對象,但是又将RequestContext對象中的兩個屬性提取到自己的類中.如果僅僅是為了使用友善而這麼做, 個人認為不是一個好的設計.資料對象的存儲職責也應該明确,使用ControllerContext.RequestContext.RouteData 的方式更容易被人了解.

PS:這種方式類似于方法内聯.對于屬性JIT為了效率會幫助我們做内聯.而僅僅是為了使用友善.

2.IView 與 View對象的關系

是以從系統的角度上看, 實作了IView接口的對象才是View.

但是從實作效果上看, 具體的aspx或者ascx頁面才是View.

當第一次看到IView接口時我認為它應該是"View角色"需要實作的接口. 但是結果并不是這樣.

在我們的系統中View對象應該是aspx或者ascx檔案. 而且并不是所有的ActionResult都需要找到aspx或者ascx檔案, 事實上隻有PartialViewResult 和 ViewResult 才會去尋找View對象.其他的ActionResult要麼是傳回檔案, 要麼是跳轉等等.

那麼兩者的關系到底是怎樣的? 其實其中的過程需要牽扯到這幾個接口和類:

IViewEngine, ViewEngineResult, ViewEngineCollection

ViewEngine是View引擎, ViewEngineCollection是一個引擎集合,裡面儲存了各種尋找View的引擎.但是在目前的源代碼中隻有WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine

這一系列WebForm使用的引擎.引擎的作用有兩個:

1.尋找Page/使用者控件的路徑

2.根據路徑建立IView對象.也就是根據頁面的實體檔案建立IView接口對象.

而且目前實作了IView接口的對象也隻有一個:

WebFormView

WebFormViewEngine 根據頁面路徑, 将一個頁面位址轉化為一個WebFormView對象,也就是一個IView接口對象.

至此IView接口和Page頁面類仍然沒有任何關系, IView對象隻是儲存了頁面的實體路徑.

接着在IView的Render事件中,根據實體路徑建立了一個頁面的object執行個體,注意看這一段代碼:

object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object)); if (viewInstance == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_ViewCouldNotBeCreated, ViewPath)); } ViewPage viewPage = viewInstance as ViewPage; if (viewPage != null) { RenderViewPage(viewContext, viewPage); return; } ViewUserControl viewUserControl = viewInstance as ViewUserControl; if (viewUserControl != null) { RenderViewUserControl(viewContext, viewUserControl); return; }      

viewInstance 就是通過實體路徑建立的頁面對象.但是他的類型是object, 而且程式嘗試将其分别轉化為ViewPage對象和ViewUserControl對象.

我想很多人都看到了這裡的設計不足.現在我們隻能"約定": 所有的MVC中的頁面對象都必須繼承自ViewPage或者ViewUserControl類, 否則程式就會出錯.産生這種不足的原因就是IView接口和ViewPage沒有任何的耦合性, 完全是寫死進去的.

為什麼不讓頁面直接實作IView接口? 然後嘗試将頁面轉化為IView接口對象, 而不是ViewPage, 這樣才是好的設計. 其實微軟知道什麼是好的設計, 我猜測他們遇到的困難是Page對象和IView接口的沖突. 因為兩者都需要Render. 如果在IView中定義自己的Render名稱, 那就意味着ASP.NET MVC開發小組要自己處理頁面的顯示邏輯, 而現在ASP.NET WebForm模式下面的頁面顯示引擎又不能複用, 重新開發自己的一套顯示引擎成本又太大, 才出此下策.

以上隻是猜測.這種設計的缺陷雖然可以接受, 但是真的是讓我好幾天陷入了看不懂代碼的痛苦之中.還好, 現在可以解脫了.

七.如何在MVC項目中使用MVC源代碼項目

另外在為了跟蹤實作過程, 我将ASP.NET MVC的源代碼項目添加到了執行個體項目中, 其中有一些需要注意的地方:

1. 将執行個體項目中的System.Web.Mvc引用删除, 改成項目引用.

2. 需要在Web.Config中注釋掉程式集引用:

<compilation debug="true"> <assemblies> <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <!-- <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>--> <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> </assemblies> </compilation>      

注釋掉的程式集存在于GAC中, 但是我們現在不希望使用GAC中的程式集, 而是引用項目.

3. 将View目錄下的Web.Config中的所有System.Web.Mvc相關的 PublicKeyToken 都修改為 null:

<pages validateRequest="false" pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <controls> <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" /> </controls> </pages>      

八.總結

首先很抱歉在本系列文章開篇時承諾的每日一篇僅僅堅持了2天.具體原因就不解釋了.這篇文章的出爐曆時半個月, 并且經曆了ASP.NET MVC從RC,RC2直到最近的1.0版本的演變. 在檢視MVC源代碼上花費了大量的時間, 希望付出的努力能夠為大家研究學習ASP.NET MVC帶來幫助.

部落格園大道至簡

http://www.cnblogs.com/jams742003/

轉載請注明:部落格園

繼續閱讀