33
技術社區[雲棲]
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