Controller激活系統最終通過注冊的ControllerFactory創建相應的Conroller對象,如果沒有對ControllerFactory類型或者類型進行顯式注冊(通過調用當前ControllerBuilder的SetControllerFactory方法),默認使用的是一個DefaultControllerFactory對象,我們現在就來討論實現在DefaultControllerFactory類型中的默認Controller激活機制。
目錄
一、Controller類型的解析
實例演示:創建一個自定義ControllerFactory模擬Controller默認激活機制
二、 Controller類型的緩存
三、 Controller的釋放
四、會話狀態行為的控制
一、Controller類型的解析
激活目標Controller對象的前提是能夠正確解析出對應的Controller類型。對於DefaultControllerFactory來,用於解析目標Controller類型的信息包括:通過與當前請求匹配的路由對象生成的RouteData(其中包含Controller的名稱和命名空間)和包含在當前ControllerBuilder中的命名空間。很對讀者可以首先想到的是通過Controller名稱得到對應的類型,並通過命名空間組成Controller類型的全名,最后遍歷所有程序集以此名稱去加載相應的類型即可。
這貌似一個不錯的解決方案,實際上則完全不可行。不要忘了作為請求地址URL一部分的Controller名稱是不區分大小寫的,而類型名稱則是區分大小的;不論是注冊路由時指定的命名空間還是當前ControllerBuilder的默認命名空間,有可能是包含統配符(*)。由於我們不能通過給定的Controller名稱和命名空間得到Controller的真實類型名稱,自然就不可能通過名稱去解析Controller的類型了。
ASP.NET MVC的Controller激活系統反其道而行之。它先遍歷通過BuildManager的靜態方法GetReferencedAssemblies方法得到的編譯Web應用所使用的程序集,通過反射得到所有實現了接口IController的類型,最后通過給定的Controller的名稱和命名空間作為匹配條件在這個預先獲取的類型列表中得到目標Controller的類型。
實例演示:創建一個自定義ControllerFactory模擬Controller默認激活機制
為了讓讀者對默認采用的Controller激活機制,尤其是Controller類型的解析機制有一個深刻的認識,我們通過一個自定義的ControllerFactory來模擬其中的實現。由於我們采用反射的方式來創建Controller對象,所以我們將該自定義ControllerFactory起名為ReflelctionControllerFactory。[源代碼從這里下載]
1: public class ReflelctionControllerFactory : IControllerFactory
2: {
3: //其他成員
4: private static List<Type> controllerTypes;
5: static ReflelctionControllerFactory()
6: {
7: controllerTypes = new List<Type>();
8: foreach (Assembly assembly in BuildManager.GetReferencedAssemblies())
9: {
10: controllerTypes.AddRange(assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type)));
11: }
12: }
13:
14: public IController CreateController(RequestContext requestContext, string controllerName)
15: {
16: Type controllerType = this.GetControllerType(requestContext.RouteData, controllerName);
17: if (null == controllerType)
18: {
19: throw new HttpException(404, "No controller found");
20: }
21: return (IController)Activator.CreateInstance(controllerType);
22: }
23:
24: private static bool IsNamespaceMatch(string requestedNamespace, string targetNamespace)
25: {
26: if (!requestedNamespace.EndsWith(".*", StringComparison.OrdinalIgnoreCase))
27: {
28: return string.Equals(requestedNamespace, targetNamespace, StringComparison.OrdinalIgnoreCase);
29: }
30: requestedNamespace = requestedNamespace.Substring(0, requestedNamespace.Length - ".*".Length);
31: if (!targetNamespace.StartsWith(requestedNamespace, StringComparison.OrdinalIgnoreCase))
32: {
33: return false;
34: }
35: return ((requestedNamespace.Length == targetNamespace.Length) || (targetNamespace[requestedNamespace.Length] == '.'));
36: }
37:
38: private Type GetControllerType(IEnumerable<string> namespaces, Type[] controllerTypes)
39: {
40: var types = (from type in controllerTypes
41: where namespaces.Any(ns => IsNamespaceMatch(ns, type.Namespace))
42: select type).ToArray();
43: switch (types.Length)
44: {
45: case 0: return null;
46: case 1: return types[0];
47: default: throw new InvalidOperationException("Multiple types were found that match the requested controller name.");
48: }
49: }
50:
51: protected virtual Type GetControllerType(RouteData routeData, string controllerName)
52: {
53: //省略實現
54: }
55: }
如上面的代碼片斷所示,ReflelctionControllerFactory具有一個靜態的controllerTypes字段由於保存所有Controller的類型。在靜態構造函數中,我們調用BuildManager的GetReferencedAssemblies方法得到所有用於編譯Web應用的程序集,並從中得到所有實現了IController接口的類型,這些類型全部被添加到通過靜態字段controllerTypes表示的類型列表。
Controller類型的解析實現在受保護的GetControllerType方法中,在用於最終激活Controller對象的CreateController方法中,我們通過調用該方法得到與指定RequestContext和Controller名稱相匹配的Controller類型,最終通過調用Activator的靜態方法CreateInstance根據該類型創建相應的Controller對象。如果不能找到匹配的Controller類型(GetControllerType方法返回Null),則拋出一個HTTP狀態為404的HttpException。
ReflelctionControllerFactory中定義了兩個輔助方法,IsNamespaceMatch用於判斷Controller類型真正的命名空間是否與指定的命名空間(可能包含統配符)相匹配,在進行字符比較過程中是忽略大小寫的。私有方法GetControllerType根據指定的命名空間列表和類型名稱匹配的類型數組得到一個完全匹配的Controller類型。如果得到多個匹配的類型,直接拋出InvalidOperation異常,並提示具有多個匹配的Controller類型;如果找不到匹配類型,則返回Null。
在如下所示的用於解析Controller類型的GetControllerType方法中,我們從預先得到的所有Controller類型列表中篩選出類型名稱與傳入的Controller名稱相匹配的類型。我們首先通過路由對象的命名空間對 之前 得到的類型列表進行進一步篩選,如果能夠找到一個唯一的類型,則直接將其作為Controller的類型返回。為了確定是否采用后備命名空間對Controller類型進行解析,我們從作為參數參數的RouteData對象的DataTokens中得到獲取一個Key為“UseNamespaceFallback”的元素,如果該元素存在並且值為False,則直接返回Null。
如果RouteData的DataTokens中不存在這樣一個UseNamespaceFallback元素,或者它的值為True,則首先里當前ControllerBuilder的默認命名空間列表進一步對Controller類型進行解析,如果存在唯一的類型則直接當作目標Controller類型返回。如果通過兩組命名空間均不能得到一個匹配的ControllerType,並且只存在唯一一個與傳入的Controller名稱相匹配的類型,則直接將該類型作為目標Controller返回。如果這樣的類型具有多個,則直接拋出InvalidOperationException異常。
1: public class ReflelctionControllerFactory : IControllerFactory
2: {
3: //其他成員
4: protected virtual Type GetControllerType (RouteData routeData, string controllerName)
5: {
6: //根據類型名稱篩選
7: var types = controllerTypes.Where(type => string.Compare(controllerName + "Controller", type.Name, true) == 0).ToArray();
8: if (types.Length == 0)
9: {
10: return null;
11: }
12:
13: //通過路由對象的命名空間進行匹配
14: var namespaces = routeData.DataTokens["Namespaces"] as IEnumerable<string>;
15: namespaces = namespaces ?? new string[0];
16: Type contrllerType = this.GetControllerType(namespaces, types);
17: if (null != contrllerType)
18: {
19: return contrllerType;
20: }
21:
22: //是否允許采用后備命名空間
23: bool useNamespaceFallback = true;
24: if (null != routeData.DataTokens["UseNamespaceFallback"])
25: {
26: useNamespaceFallback = (bool)(routeData.DataTokens["UseNamespaceFallback"]);
27: }
28:
29: //如果不允許采用后備命名空間,返回Null
30: if (!useNamespaceFallback)
31: {
32: return null;
33: }
34:
35: //通過當前ControllerBuilder的默認命名空間進行匹配
36: contrllerType = this.GetControllerType(ControllerBuilder.Current.DefaultNamespaces, types);
37: if (null != contrllerType)
38: {
39: return contrllerType;
40: }
41:
42: //如果只存在一個類型名稱匹配的Controller,則返回之
43: if (types.Length == 1)
44: {
45: return types[0];
46: }
47:
48: //如果具有多個類型名稱匹配的Controller,則拋出異常
49: throw new InvalidOperationException("Multiple types were found that match the requested controller name.");
50: }
51: }
二、 Controller類型的緩存
為了避免通過遍歷所有程序集對目標Controller類型的解析,ASP.NET MVC對解析出來的Controller類型進行了緩存以提升性能。與針對用於Area注冊的AreaRegistration類型的緩存類似,Controller激活系統同樣采用基於文件的緩存策略,而用於保存Controller類型列表的名為MVC-ControllerTypeCache.xml的文件保存在ASP.NET的臨時目錄下面。具體的路徑如下,其中第一個針對寄宿於IIS中的Web應用,后者針對直接通過Visual Studio Developer Server作為宿主的應用。而用於保存所有AreaRegistration類型列表的MVC-AreaRegistrationTypeCache.xml文件也保存在這個目錄下面。
- %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\{appname}\...\...\UserCache\
- %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\root\...\...\UserCache\
對針對Web應用被啟動后的第一個請求時,Controller激活系統會讀取這個用於緩存所有Controller類型列表的ControllerTypeCache.xml文件並反序列化成一個List<Type>對象。只有在該列表為空的時候才會通過遍歷程序集和反射的方式得到所有實現了接口IController的公有類型,而被解析出來的Controller類型重寫被寫入ControllerTypeCache.xml文件中。這個通過讀取緩存文件或者重新解析出來的Controller類型列表被保存到內容中,在Web應用活動期間內被Controller激活系統所用。
下面的XML片斷反映了這個用於Controller類型列表緩存的ControllerTypeCache.xml文件的結構,我們可以看出它包含了所有的Controller類型的全名和所在的程序集和托管模塊信息。
1: <?xml version="1.0" encoding="utf-8"?>
2: <!--This file is automatically generated. Please do not modify the contents of this file.-->
3: <typeCache lastModified="3/22/2012 1:18:49 PM" mvcVersionId="80365b23-7a1d-42b2-9e7d-cc6f5694c6d1">
4: <assembly name="Artech.Admin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
5: <module versionId="eb343e3f-2d63-4665-a12a-29fb30dceeed">
6: <type> Artech.Admin .HomeController</type>
7: <type> Artech.Admin .EmployeeController </type>
8: </module>
9: </assembly>
10: <assembly name="Artech.Portal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
11: <module versionId=" 3717F116-35EE-425F-A1AE-EB4267497D8C ">
12: <type>Artech. Portal.Controllers.HomeController</type>
13: <type>Artech. Portal.ProductsController</type>
14: </module>
15: </assembly>
16: </typeCache>
三、 Controller的釋放
作為激活Controller對象的ControllerFactory不僅僅用於創建目標Controller對象,還具有兩個額外的功能,即通過ReleaseController方法對激活的Controller對象進行釋放和回收,以及通過GetControllerSessionBehavior返回用於控制當前會話狀態行為的SessionStateBehavior枚舉。
對於默認使用DefaultControllerFactory來說,針對Controller對象的釋放操作很簡單:如果Controller類型實現了IDisposable接口,則直接調用其Dispose方法即可;否則直接忽略。我們將這個邏輯也實現在了我們自定義的ReflelctionControllerFactory中。
1: public class ReflelctionControllerFactory : IControllerFactory
2: {
3: //其他操作
4: public void ReleaseController(IController controller)
5: {
6: IDisposable disposable = controller as IDisposable;
7: if (null != disposable)
8: {
9: disposable.Dispose();
10: }
11: }
12: }
四、會話狀態行為的控制
至於用於返回SessionStateBehavior枚舉的GetControllerSessionBehavior方法來說,在默認的情況下的返回值為SessionStateBehavior.Default。通過前面的介紹我們知道在這種情況下具體的會話狀態行為取決於創建的HttpHandler所實現的標記接口。對於ASP.NET MVC應用來說,默認用於處理請求的HttpHandler是一個叫做MvcHandler的對象,如下面的代碼片斷所示,HttpHandler實現了IRequiresSessionState接口,意味着默認情況下會話狀態是可讀寫的(相當於SessionStateBehavior.Requried)。
1: public class MvcHandler :
2: IHttpAsyncHandler,
3: IHttpHandler,
4: IRequiresSessionState
5: {
6: //其他成員
7: }
不過我們可以通過在Controller類型上應用SessionStateAttribute特性來具體控制會話狀態行為。如下面的代碼片斷所示,SessionStateAttribute具有一個SessionStateBehavior類型的只讀屬性Behavior用於返回具體行為設置的會話狀態行為選項,該屬性是在構造函數中被初始化的。
1: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
2: public sealed class SessionStateAttribute : Attribute
3: {
4: public SessionStateAttribute(SessionStateBehavior behavior);
5: public SessionStateBehavior Behavior { get; }
6: }
也就是說DefaultControllerFactory會通過解析出來的Controller類型得到應用在上面的SessionStateAttribute特性,如果這樣的特性存在則直接返回它的Behavior屬性所表示的SessionStateBehavior枚舉;如果不存在則返回SessionStateBehavior.Default,具體的邏輯反映在我們自定義的ReflelctionControllerFactory的GetControllerSessionBehavior方法中。
1: public class ReflelctionControllerFactory : IControllerFactory
2: {
3: //其他成員
4: public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
5: {
6: Type controllerType = this.GetControllerType(requestContext.RouteData, controllerName);
7: if (null == controllerType)
8: {
9: return SessionStateBehavior.Default;
10: }
11: SessionStateAttribute attribute = controllerType.GetCustomAttributes(true).OfType<SessionStateAttribute>()
12: .FirstOrDefault();
13: attribute = attribute ?? new SessionStateAttribute(SessionStateBehavior.Default);
14: return attribute.Behavior;
15: }
16: }