Welcome 微信登录
编程资源 图片资源库 蚂蚁家优选

首页 / 网页编程 / ASP.NET / ASP.NET MVC Controller激活系统详解:默认实现

ASP.NET MVC Controller激活系统详解:默认实现2012-11-16 cnblogs ArtechController激活系统最终通过注册的ControllerFactory创建相应的Conroller对象,如果没有对ControllerFactory类型或者类型进行显式注册(通过调用当前ControllerBuilder的SetControllerFactory方法),默认使用的是一个DefaultControllerFactory对象,我们现在就来讨论实现在DefaultControllerFactory类型中的默认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。