上一篇文章“.NET/ASP.NET MVC Controller 控制器(一:深入解析控制器運作原理)”詳細的講解了MvcHandler對象内部的基本流程邏輯,這基本的流程邏輯為我們後面的學習起到鋪墊作用,當我們能正确的搞懂它的内部執行流程後,我們就可以順藤摸瓜的去挖掘每個邏輯環節中的詳細邏輯; 通過前面兩篇文章的介紹,我們基本上能搞清楚一個Url請求是如何借助于UrlRoutingModule子產品順利穿過ASP.NET基礎架構到達應用架構的過程,當UrlRoutingModule處理過後将RouteData對象封裝在RequestContext請求上下文中傳入到MvcHandler對象,然後MvcHandler對象通過IControllerFactory接口根據從RouteData中擷取到controllername控制器名稱字元串建立具體的IController對象執行個體;
閱讀目錄:
- 1.開篇介紹
- 2.ASP.NETMVC IControllerFactory 控制器工廠接口
- 3.ASP.NETMVC DefaultControllerFactory 預設控制器工廠
- 4.ASP.NETMVC ControllerBuilder 控制器建立入口設定
- 5.ASP.NETMVC 自定義IControllerFactory
1】開篇介紹
上一篇文章“.NET/ASP.NET MVC Controller 控制器(一:深入解析控制器運作原理)”詳細的講解了MvcHandler對象内部的基本流程邏輯,這基本的流程邏輯為我們後面的學習起到鋪墊作用,當我們能正确的搞懂它的内部執行流程後,我們就可以順藤摸瓜的去挖掘每個邏輯環節中的詳細邏輯;
通過前面兩篇文章的介紹,我們基本上能搞清楚一個Url請求是如何借助于UrlRoutingModule子產品順利穿過ASP.NET基礎架構到達應用架構的過程,當UrlRoutingModule處理過後将RouteData對象封裝在RequestContext請求上下文中傳入到MvcHandler對象,然後MvcHandler對象通過IControllerFactory接口根據從RouteData中擷取到controllername控制器名稱字元串建立具體的IController對象執行個體;
這基本的流程我們是清晰了,但是我們并不太清楚IControllerFactory背後所發生的一切,到底誰作為IControllerFactory預設實作的,它又有着怎樣的擴充入口讓我們來擴充建立過程,這值得一探究竟;
那麼這篇文章讓我們來分析一下IControllerFactory的背後所發生的事情,我們是否能從中學到什麼設計思想;
2】ASP.NETMVC IControllerFactory 控制器工廠接口
既然能将ControllerFactory提取出接口來,那麼對于IController的建立将是一個非常寬松的過程;簡單的設想一下,如果不将Factory提出接口來,那麼對于IController的建立将是一個很直覺的過程,但是ASP.NETMVC将IController建立不是簡單的使用一個ControllerFactory來解決,而是将這個建立過程設計的很松散,目的是為了擴充性友善,換句話說我們完全可以自定義一個Factroy來替代這個建立過程,也可以基于系統内部的Factroy來擴充一下;
MvcHandler使用IControllerFactroy建立出相應IController對象,那麼首先我們需要搞清楚MvcHandler通過什麼方式擷取到實作IControllerFactory接口的;
其實在MvcHandler中并不是直接使用IControllerFactroy的相關實作,而是使用了ControllerBuilder對象,這個對象是一個單例模式的實作;MvcHanlder通過ControllerBuilder對象擷取到一個執行個體,然後通過ControllerBuilder建立出IControllerFactory實作;
1 internal ControllerBuilder ControllerBuilder {
2 get {
3 if (_controllerBuilder == null) {
4 _controllerBuilder = ControllerBuilder.Current;
5 }
6 return _controllerBuilder;
7 }
8 set {
9 _controllerBuilder = value;
10 }
11 }
12
13 factory = ControllerBuilder.GetControllerFactory();
可以簡單的了解為,ControllerBuilder管理着IControllerFactory的建立過程,MvcHanlder通過擷取ControllerBuilder的全局執行個體,然後調用其方法GetControllerFactory,得到可以使用的IControllerFactory實作;
圖1:
ControllerBuilder的設計很巧妙,它将IControllerFactory的實作為我們敞開了大門,我們可以通過這個入口做很多事情;
我們看一下IControllerFactroy接口的定義:
1 public interface IControllerFactory {
2 IController CreateController(RequestContext requestContext, string controllerName);
3 SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
4 void ReleaseController(IController controller);
5 }
接口中定義了三個方法,第一個方法CreateController很好了解,根據方法的第二個參數controllerName建立Controller執行個體;第二個方法GetControllerSessionBehavior方法是用來擷取controllerName所代表的Controller的Session行為的,該行為是通過SessionStateAttribute特性表示;第三個方法ReleaseController方法是用在最後釋放Controller的:
1 public virtual void ReleaseController(IController controller) {
2 IDisposable disposable = controller as IDisposable;
3 if (disposable != null) {
4 disposable.Dispose();
5 }
6 }
由于Controller繼承自IDisposable接口,是以在方法内部是直接調用Dispose方法來釋放資源;這裡需要注意的是,Controller對IDisposable接口的實作是virtual修飾符:
1 protected virtual void Dispose(bool disposing) {
2 }
這就很友善我們通過重寫此方法的方式來釋放一些其他資源;
3】ASP.NETMVC DefaultControllerFactory 預設控制器工廠
在ASP.NETMVC内部有一個預設的Factroy(DefaultControllerFactroy),DefaultControllerFactroy實作了核心的建立IController代碼,這為我們的擴充提供了很好的接口;
通過調用IControllerFactory接口的CreateController(RequestContext requestContext, string controllerName) 方法,将進入到DefaultControllerFactory實作中,首要任務就是要根據controllerName名稱找到對應的ContorllerType,然後才能建立具體的執行個體;
1 object routeNamespacesObj;
2 Type match;
3 if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj)) {
4 IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
5 if (routeNamespaces != null && routeNamespaces.Any()) {
6 HashSet<string> nsHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
7 match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, nsHash);
8
9 // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true"
10 if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"])) {
11 // got a match or the route requested we stop looking
12 return match;
13 }
14 }
15 }
首先根據請求的路由資料RouteData,查找設定的命名空間集合,然後使用命名空間和控制器名稱擷取Type,如果Type!=null并且沒有開啟後被命名空間則直接傳回Type;
3.1】Controller中的AreaRegistration命名空間
在DefaultControllerFactroy内部使用到了兩組命名空間來作為查找Controller的NameSpace,第一個是我們在配置Route資料的時候設定的:
1 context.MapRoute(name: "api.order.default", url: "api/order/{controller}/{action}/{orderid}",
2 defaults: new { controller = "OrderController", action = "GetOrderOperationDatetime", orderid = "1001" },
3 namespaces: new string[] { "Api.Order" });
而第二個我們一般都不會用它的,它是作為AreaRegistration後備命名空間而存在的,是在ControllerBuilder中設定的:
1 ControllerBuilder.Current.DefaultNamespaces.Add("MvcApplication4.ApiOrder");
對後備命名空間的指派是在AreaRegistrationContext中的MapRoute(string name, string url, object defaults, object constraints, string[] namespaces) 方法中完成的:
1 if (namespaces == null && Namespaces != null) {
2 namespaces = Namespaces.ToArray();
3 }
1 Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
2 route.DataTokens["area"] = AreaName;
3
4 // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
5 // controllers belonging to other areas
6 bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
7 route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
8
9 return route;
由于AreaRegistration可以讓我們對Controller的設計不局限于ASP.NETMVCWeb程式中,而可以将Controller獨立出去進行子產品化設計,是以需要提供有關Area的特殊命名空間查找方式;
4】ASP.NETMVC ControllerBuilder 控制器建立入口設定
ControllerBuilder作為Controller建立的設定入口,可以用來設定ControllerFactory替換系統預設的DefaultControllerFactory,ControllerBuilder是Controller的建立過程架構擴充入口,可以借助ControllerBuilder友善做很多設定;
1 internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) {
2 _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>(
3 () => _factoryThunk(),
4 new DefaultControllerFactory { ControllerBuilder = this },
5 "ControllerBuilder.GetControllerFactory"
6 );
7 }
在ControllerBuilder的構造函數中,初始化了一個SingleServiceResolver<IControllerFactory>類型的Resolver,目的是為了對Factory實作IOC方式的擷取;在代碼中,執行個體化了一個DefaultControllerFactory類型的執行個體作為預設的Factory,比較重要的是将ControllerBuilder做為參數設定到了ControllerBuilder屬性中,目的是為了能在後面解析Controller命名空間的時候用到;
1 public HashSet<string> DefaultNamespaces {
2 get {
3 return _namespaces;
4 }
5 }
在此我們可以設定統一的命名空間,由于我們在設定Route的時候,都需要設定namesapce字段,但是如果有很多這樣的Route的時候就很麻煩,我們可以通過此方式進行統一的設定;
1 public void SetControllerFactory(IControllerFactory controllerFactory) {
2 if (controllerFactory == null) {
3 throw new ArgumentNullException("controllerFactory");
4 }
5
6 _factoryThunk = () => controllerFactory;
7 }
還有一個比較重要的就是設定自定義的ControllerFactory,在方法SetControllerFactory中,我們可以設定一個IControllerFactory類型的對象,就可以接管系統預設的DefaultControllerFactory對象,包括後面的所有的IController緩存政策;
圖2:
基本上我們可以通過ControllerBuilder進入到ControllerFactroy的建立環節來,使用SetControllerFactory方法直接将我們自定義的IControllerFactroy傳入即可;
5】ASP.NETMVC 自定義IControllerFactory
既然知道了ContollerBulder可以使我們更改系統預設的控制器工廠,那麼我們通過怎樣的方式使用現在的Factroy;大緻上我們隻需要繼承自DefaultControllerFactory然後進行相應的擴充即可;
1 public class CustomControllerFactory : DefaultControllerFactory
2 {
3 protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
4 {
5 Console.WriteLine(string.Format("{0}is create.", controllerType.Name));
6 return base.GetControllerInstance(requestContext, controllerType);
7 }
8 }
現在假設我們需要在系統建立所有Controller的時候能記錄下建立的記錄資訊,這樣就很友善的完成了,我們隻需要在系統初始化的地方進行設定:
1 ControllerBuilder.Current.SetControllerFactory(new Api.Order.CustomControllerFactory());
這樣我們就接管了ControllerFactory的部分功能;
作者:王清培
出處:http://www.cnblogs.com/wangiqngpei557/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。