.NET/ASP.NET MVC Controller 控制器(深入解析控制器執行原理)

王清培發表於2013-10-27

閱讀目錄:

  • 1.開篇介紹
  • 2.ASP.NETMVC Controller 控制器的入口(Controller的執行流程)
  • 3.ASP.NETMVC Controller 控制器的入口(Controller的繼承體系)
  • 4.ASP.NETMVC IController Factory 控制器工廠(Controller的建立)

1】開篇介紹

經過前一篇文章.NET/ASP.NET Routing路由(深入解析路由系統架構原理) 的講解,我們對ASP.NETRouting路由系統的整個執行機制有了一個基本的瞭解;當我們能清楚的知道Url是如何被解析成RouteData物件時,下面就是這些路由資料是如何被後面的應用框架所使用的,而通往應用框架的入口是MvcRouteHandler物件;

這篇文章將繼續講解通過路由後的ASP.NETMVC Controller控制器是如何被載入、啟用並且執行的;跟控制器相關的一套物件模型是被MvcHandler物件作為源頭呼叫起來的,也就是說,當我們穿過UrlRoutingModule物件後,並且成功的獲取到應用框架配置的路由資料後,下面將進入IHttpHandler介面,而這個介面真是我們初始化RouteData物件時設定的應用框架入口,ASP.NETMVC所使用的是MvcHandler物件;

MvcRouteHandler物件是UrlRoutingModuleMvcHandler物件的聯結器,只有MvcRouteHandler物件能成功執行後,方能進入到MvcHandler物件中,後續的一切運轉才能順利執行;

2】ASP.NETMVC Controller 控制器的入口(Controller的執行流程)

在系統剛啟動的時候,也就是在Global.asax.cs檔案裡面我們配置了Http客戶端請求伺服器的Url模板;在路由解析模組(UrlRoutingModule)裡面,它將通過字串級別的操作,解析出我們Url模板中的{Controller}/{Action}等的佔位符變數;所以這個時候Controller的概念對我們來說還只是一個字串而已,而到了目前的這個Controller控制器解析的位置其實已經和路由基本沒關係了,因為我們穿過了路由模組到達了Controller解析的環節;Controller解析已經屬於ASP.NETMVC應用框架的範圍,我們可以簡單的將路由解析(UrlRoutingModule)的過程視為將請求的Url(含有資料的Url)與我們配置的Url模板進行模式匹配的過程,得出匹配後的Url資料(RouteData),然後將Url資料並且連同當前請求上下文一起封裝成RequestContext物件(RouteData、HttpContextBase)傳入到Controller解析的環節,也就是MvcHandler中,作為MvcHandler建構函式的引數;

當MvcHandler接管控制權之後它需要準備好對Controller的解析和執行,但是Controller併發一個簡單的物件,它有一個複雜的繼承體系和使用方式,原因在於它需要協調多方面的工作所以變的有很複雜;

根據MVC的架構模式理論便知道Controller是協調Model與View的中間紐帶,它既要管理好Model的執行,也要管理好View的呈現;而原本MVC的架構模式提出的背景是在WinFrom的情況下,也就是傳統C/S結構的系統;WinFrom結構的系統有一個好處就是它的執行很方便,從View的展現收集資料到Controller的排程執行Model會容易完成,但是ASP.NETMVC是建立在ASP.NET WEB背景之下的MVC模式框架,所以這個時候對Controller的啟用會變的相當麻煩,因為在傳輸過程中Controller已經是字串形式,如果是在C/S結構中那麼Controller對於每次處理一樣的View不會每次都進行啟用;既然每次都需要啟用就需要進行快取策略,快取策略只是Controller中的一個關鍵點,需要明白的是Controller的確需要做很多事情;

圖1:

根據上圖的執行順序,能看出Controller控制器扮演著一個很重要的角色,所有的執行、返回值、檢視呈現均需要通過它來管理排程;當然本章的重點是搞清楚此圖中的第一環節,Controller是如何被載入啟用的,這裡面將涉及到眾多的輔助物件模型,比如:ControllerFactory控制器工廠,而控制器工廠又將藉助ControllerTypeCache來快取Controller物件,而ConrollerTypeCache又將藉助TypeCacheSerializer來對Controller快取檔案的序列化;

3.ASP.NETMVC Controller 控制器的入口(Controller的繼承體系)

Controller控制器既然扮演著重要的角色,那麼它就不會是一個簡單的物件結構,它有著一個複雜的繼承體系和物件模型支撐它來完成這些艱鉅的任務;Controller要想能夠執行起來,就需要搞清楚它有哪些執行入口,而需要知道它有哪些執行入口我們就需要搞清楚它的繼承體系;入口的最高層抽象在哪一層,這樣我們才能舉一反三的擴充套件Controller的眾多重要的功能;

首先我們瞭解到Controller的頂層抽象是IController介面,然後接著是ControllerBase抽象類實現了這個介面,而作為頂層抽象的實現ControllerBase完成了從IController介面繼承下來的方法;

1 public interface IController {
2     void Execute(RequestContext requestContext);
3 } 

通過該程式碼段可以看出,Controller的執行需要一個RequestContext物件,而這個物件真是UrlRoutingModule環節所完成的結果,RequestContext物件內部封裝了在Request階段所獲得的請求資料,裡面包括了跟Http相關的請求上下文(HttpContextBase),最重要的是路由資料物件(RouteData);而控制器的執行必須需要RouteData中的有關Controller資料物件,也就是從請求Url中通過模式匹配出來的{Controller}部分的字串;

ControllerBase定義了Controller使用到的部分公共屬性,比如:用來儲存臨時資料的TempData,用來返回到View中的Model資料物件ViewBag、ViewData;並且初始化了ControllerContext物件,用來作為後續Controller使用的資料容器和操作上下文;

1 protected virtual void Initialize(RequestContext requestContext) {
2     ControllerContext = new ControllerContext(requestContext, this);
3 } 

在ControllerBase中將對IController.Execute(RequestContext requestContext)方法呼叫轉到了protected abstract void ExecuteCore()方法中;這是一個典型的模板方法模式,下面的繼承類Controller,只需要接著protected abstract void ExecuteCore()方法就能和ControllerBase銜接上;

public abstract class Controller : ControllerBase

Controller類繼承自ControllerBase,而Controller的任務只需要完成ExecuteCore()方法;

 1 protected override void ExecuteCore() { 
 2 
 3            PossiblyLoadTempData();
 4            try {
 5               string actionName = RouteData.GetRequiredString("action");
 6                if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
 7                    HandleUnknownAction(actionName);
 8                }
 9            }
10            finally {
11                PossiblySaveTempData();
12            }
13        } 

Controller.ExecuteCore()的程式碼將從RouteData中獲取執行action的名稱,然後通過一個ActionInvoke的元件進行Action的呼叫,當Action被執行的時候將進入到我們繼承的Controller,如:HomeController:Controller中,在我們自定的Controller中的方法都將被視為Action的匹配目標之一;

圖2:

根據上圖的指示,ControllerBase首先是實現IController介面,完成了對Execute(RequestContext requestContext)方法的實現,然後Controller繼承ControllerBase類,重寫了模板方法ExecuteCore()方法,然後我們自定義的HomeController其實是Action的容器,當Controller的ExecuteCore()方法執行時將通過ActionInvoke類進行對HomeController中的方法呼叫;

4.ASP.NETMVC IController Factory 控制器工廠(Controller的建立)

當清楚了Controller的繼承體系之後,下面回到MvcHandler呼叫的環節;MvcHandler繼承自IHttpHandler介面 ,表示它將是ASP.NET真正執行請求處理的地方;在MvcHandler處理請求的方法中ProcessRequest(HttpContextBase httpContext),將通過IControllerFactory介面建立IController介面;

IControllerFactory介面是控制器工廠介面,專門用來實現建立IController物件工廠類,在ASP.NETMVC內部有一個實現了IControllerFactory介面的預設工廠類DefaultControllerFactory,ASP.NETMVC內部是用這個類來建立IController物件的;

1 factory = ControllerBuilder.GetControllerFactory();

獲取IDefaultControllerFactory介面需要通過ControllerBuilder物件,ControllerBuilder類是專門用來管理IControllerFactory物件的,同時ControllerBuilder也是應用程式設計介面,讓自定義IControllerFactory物件成為可能;

建立IController需要我們傳入RequestContext物件和ControllerName控制器名稱;

1 // Get the controller type
2 string controllerName = RequestContext.RouteData.GetRequiredString("controller"); 
3 factory = ControllerBuilder.GetControllerFactory();
4 controller = factory.CreateController(RequestContext, controllerName);

從RequestContext.RouteData中獲取到當前請求的conroller名稱,然後用來作為factory.CreateController的引數;

圖3:

MvcHandler通過ControllerBuilder物件的靜態屬性Current獲取到ControllerBuilder物件例項,顯然ControllerBuilder是一個單例模式的物件;然後通過ControllerBuilder物件獲取到DefaultControllerFactory預設IControllerFactory工廠物件,接著利用DefaultControllerFactory建立出IController物件;

 

相關文章