专栏首页大内老AASP.NET MVC Controller激活系统详解:默认实现

ASP.NET MVC Controller激活系统详解:默认实现

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: }
ASP.NET MVC Controller激活系统详解:总体设计 ASP.NET MVC Controller激活系统详解:默认实现 ASP.NET MVC Controller激活系统详解:IoC的应用[上篇] ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [WCF安全系列]绑定、安全模式与客户端凭证类型:总结篇

    对于常用的几种绑定类型,它们都支持怎样的安全模式,以及针对各种安全模式可以采用怎样的认证方式(通过客户端凭证类型决定),这些都在前面的一系列文章中进行了详细的介...

    蒋金楠
  • 扩展UltraGrid控件实现对所有数据行的全选功能[Source Code下载]

    在前面一篇文章中,我通过对三种Infragistics 控件(UltraToolBarManager、UltraGird和UltraListView)进行扩展,...

    蒋金楠
  • WCF的Binding模型之五:绑定元素(Binding Element)

    在上面的内容中,先后介绍了信道、信道管理器、信道监听器和信道工厂。从对象的创建来讲,信道管理器是信道的创建者。说的再具体点,客户端的信道通过信道工厂创建,服务端...

    蒋金楠
  • .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Contro...

    王清培
  • 公有云发展的四大趋势

    云计算已经是当前数字经济的基础,近年来,随着行业客户上云的加速,云业务发展出现几种明显的趋势。今天我们结合实际的项目情况,与大家一起分析云计算的趋势。

    希望的田野
  • Yii2 VS thinkphp5.0

    Yii2.0使用一年多了,最近因为原来公司狗带了,换了公司,开始使用tp5.0。之前也有使用过tp3.2的框架,但是每次问及各个框架之前的区别的时候,总是不觉得...

    写PHP的老王
  • 基于selenium写微博爬虫(待续)

    萌海无涯
  • Dede cms系统迁移中出现的问题

    1 点击后台菜单不显示 解决办法,确保/data/tplcache/下的文件全部迁移过来

    lilugirl
  • ParallelX在GPU上运行Hadoop任务

    在面对大规模计算密集型算法时,MapReduce范式的表现并不总是很理想。为了解决其瓶颈,一支小型创业团队构建了名为ParallelX的产品——它将通过利用GP...

    GPUS Lady
  • Pytorch框架实现mnist手写库识别(与tensorflow对比)

    前言最近在学习过程中需要用到pytorch框架,简单学习了一下,写了一个简单的案例,记录一下pytorch中搭建一个识别网络基础的东西。对应一位博主写的tens...

    砸漏

扫码关注云+社区

领取腾讯云代金券