閱讀33 返回首頁    go 技術社區[雲棲]


ASP.NET MVC是如何運行的(3): Controller的激活

ASP.NET MVC的URL路由係統通過注冊的路由表對HTTP請求進行解析從而得到一個用於封裝路由數據的RouteData對象,而這個過程是通過自定義的UrlRoutingModule對HttpApplication的PostResolveRequestCache事件進行注冊實現的。RouteData中已經包含了目標Controller的名稱,現在我們來進一步分析真正的Controller對象是如何被激活的。我們首先需要了解一個類型為MvcRouteHandler的類型。

通過前麵的介紹我們知道繼承自RouteBase的Route類型具有一個類型為IRouteHandler接口的屬性RouteHandler,它主要的用途就是用於根據指定的請求上下文(通過一個RequestContext對象表示)來獲取一個HttpHandler對象。當GetRouteData方法被執行後,Route的RouteHandler屬性值將反映在得到的RouteData的同名屬性上。在默認的情況下,Route的RouteHandler屬性是一個MvcRouteHandler對象,如下的代碼片斷反映了這一點。

   1: public class Route : RouteBase
   2: {
   3:     //其他成員
   4:     public IRouteHandler RouteHandler { get; set; }
   5:     public Route()
   6:     {
   7:         //其他操作
   8:         this.RouteHandler = new MvcRouteHandler();
   9:     }
  10: }

對於我們這個“迷你版”的ASP.NET MVC框架來說,MvcRouteHandler是一個具有如下定義的類型。在實現的GetHttpHandler方法中,它直接返回一個MvcHandler對象。

   1: public class MvcRouteHandler: IRouteHandler
   2: {
   3:     public IHttpHandler GetHttpHandler(RequestContext requestContext)
   4:     {
   5:         return new MvcHandler(requestContext);
   6:     }
   7: }

在前麵的內容中我們已經提到整個ASP.NET MVC框架是通過自定義的HttpModule和HttpHandler對象ASP.NET進行擴展實現的。這個自定義HttpModule我們已經介紹過了,就是UrlRoutingModule,而這個自定義的HttpHandler則是我們要重點介紹的MvcHandler。

UrlRoutingModule在通過路由表解析HTTP請求得到一個用於封裝路由數據的RouteData後,或調用其RouteHandler的GetHttpHandler方法得到HttpHandler對象並注冊到當前的HTTP上下文。由於RouteData的RouteHandler來源於對應Route對象的RouteHandler,而後者在默認的情況下是一個MvcRouteHandler對象,所以默認情況下用於處理HTTP請求的就是這麼一個MvcHandler對象。MvcHandler實現了對Controller對象的激活和對相應Action方法的執行。

下麵的的代碼片斷體現了MvcHandler的整個定義,它具有一個類型為RequestContext的屬性表示被處理的當前請求上下文,該屬性在構造函數指定。在實現的ProcessRequest中實現了對Controller對象的激活和執行。

   1: public class MvcHandler: IHttpHandler
   2: {
   3:     public bool IsReusable
   4:     {
   5:         get{return false;}
   6:     }
   7:     public RequestContext RequestContext { get; private set; }
   8:     public MvcHandler(RequestContext requestContext)
   9:     {
  10:         this.RequestContext = requestContext;
  11:     }
  12:     public void ProcessRequest(HttpContext context)
  13:     {
  14:         string controllerName = this.RequestContext.RouteData.Controller;
  15:         IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
  16:         IController controller = controllerFactory.CreateController(this.RequestContext, controllerName);
  17:         controller.Execute(this.RequestContext);
  18:     }
  19: }

我們為Controller定義了一個接口IController,如下麵的代碼片斷所示,該接口具有唯一的方法Execute在MvcHandler的ProcessRequest方法中被執行,而傳入該方法的參數時表示當前請求上下文的RequestContext對象。

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

從MvcHandler的定義我們可以看到Controller對象的激活是通過工廠模式實現的,我們為Controller工廠定義了一個具有如下定義的IControllerFactory接口。IControllerFactory通過CreateController方法根據傳入的請求上下文和Controller的名稱來激活相應的Controller對象。

   1: public interface IControllerFactory
   2: {
   3: IController CreateController(RequestContext requestContext, string controllerName);
   4: }

在MvcHandler的ProcessRequest方法中,它通過ControllerBuilder的靜態屬性Current得到當前的ControllerBuilder對象,並調用GetControllerFactory方法獲得當前的ControllerFactory。然後通過從自己的RequestContext中提取的RouteData獲得Controller的名稱,最後將它連同RequestContext一起作為ContollerFactory的CreateController方法的參數進而創建具體的Controller對象。

ControllerBuilder的整個定義如下麵的代碼片斷所示,表示當前ControllerBuilder的靜態隻讀屬性的Current在靜態構造函數中被創建。SetControllerFactory和GetControllerFactory方法用於ContorllerFactory的注冊和獲取。而類型為HashSet<string>的DefaultNamespaces屬性表示默認的命名空間列表,這是為了最終解析Controller類型的需要。

   1: public class ControllerBuilder
   2: {
   3:     private Func<IControllerFactory> factoryThunk;
   4:     static ControllerBuilder()
   5:     {
   6:         Current = new ControllerBuilder();
   7:     }
   8:     public ControllerBuilder()
   9:     {
  10:         this.DefaultNamespaces = new HashSet<string>();
  11:     }
  12:     public static ControllerBuilder Current { get; private set; }
  13:     public IControllerFactory GetControllerFactory()
  14:     {
  15:         return factoryThunk();
  16:     }
  17:     public void SetControllerFactory(IControllerFactory controllerFactory)
  18:     {
  19:         factoryThunk = () => controllerFactory;
  20:     }
  21:     public HashSet<string> DefaultNamespaces { get; private set; }
  22: }

在回頭看看我們之前建立在我們自定義ASP.NET MVC框架的Web應用,我們就是通過當前的ControllerBuilder進行ControllerFactory的注冊和默認命名空間的指定的。如下麵的代碼片斷所示,我們注冊的ControllerFactory的類型為DefaultControllerFactory。

   1: public class Global : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start(object sender, EventArgs e)
   4:     {            
   5:         //其他操作
   6:         ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory());
   7:         ControllerBuilder.Current.DefaultNamespaces.Add("WebApp");
   8:     }
   9: }

作為默認ControllerFactory的DefualtControllerFactory類型定義如下。激活Controller類型的前提是能夠正確解析出Controller的真實類型。作為CreateController方法輸入參數的controllerName僅僅表示Controller的名稱,我們需要加上Controller字符後綴作為類型名稱。此外我們還需要得到類型的命名空間,而命名空間具有兩個來源,即RouteData和當前ControllerBuilder。在DefualtControllerFactory初始化過程中,我們通過BuildManager加載所有應用的程序集,並加載所有實現了接口IController的類型並保存起來,而在CreateController方法中根據Controller的名稱和命名空間從保存的Controller類型列表中得到對應的Controller類型,並通過反射的方式創建它。

   1: public class DefaultControllerFactory : IControllerFactory
   2: {
   3:     private List<Type> controllerTypes = new List<Type>();
   4:     public DefaultControllerFactory()
   5:     {
   6:         foreach (Assembly assembly in BuildManager.GetReferencedAssemblies())
   7:         {
   8:             foreach (Type type in assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type)))
   9:             {
  10:                 controllerTypes.Add(type);
  11:             }
  12:         }
  13:     }
  14:     public IController CreateController(RequestContext requestContext, string controllerName)
  15:     {
  16:         string typeName = controllerName + "Controller";
  17:         List<string> namespaces = new List<string>();
  18:         namespaces.AddRange(requestContext.RouteData.Namespaces);
  19:         namespaces.AddRange(ControllerBuilder.Current.DefaultNamespaces);
  20:         foreach (var ns in namespaces)
  21:         {
  22:             string controllerTypeName = string.Format("{0}.{1}", ns, typeName);
  23:             Type controllerType = controllerTypes.FirstOrDefault(type => string.Compare(type.FullName, controllerTypeName, true) == 0);
  24:             if (null != controllerType)
  25:             {
  26:                 return (IController)Activator.CreateInstance(controllerType); 
  27:             }
  28:         }            
  29:         return null;
  30:     }
  31: }

上麵我們詳細地介紹了Controller的激活原理,我們現在講關注點返回到Controller自身。通過實現IContrller接口,我們為具有的Controller定義了一個具有如下定義的ControllerBase抽象基類。從中我們可以看到在實現的Execute方法中,ControllerBase通過一個實現了接口IActionInvoker的對象完成了針對Action方法的執行。

   1: public abstract class ControllerBase: IController
   2: {
   3:     protected IActionInvoker ActionInvoker { get; set; }
   4:     public ControllerBase()
   5:     {
   6:         this.ActionInvoker = new ControllerActionInvoker();
   7:     }
   8:     public void Execute(RequestContext requestContext)
   9:     {
  10:         ControllerContext context = new ControllerContext { RequestContext = requestContext, Controller = this };
  11:         string actionName = requestContext.RouteData.ActionName;
  12:         this.ActionInvoker.InvokeAction(context, actionName);
  13:     }
  14: }

 

ASP.NET MVC是如何運行的[1]: 建立在“偽”MVC框架上的Web應用
ASP.NET MVC是如何運行的[2]: URL路由
ASP.NET MVC是如何運行的[3]: Controller擊激活
ASP.NET MVC是如何運行的[4]: Action的執行


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 12:04:34

  上一篇:go  機器視覺在掃地機器人領域的應用
  下一篇:go  淺談嵌入式開發的發展方向