Asp.net mvc 中的 Controller 的激活
Controller 激活是指根据路由系统解析出来的 Controller 的名称创建 控制器(Controller)的过程,这里的控制器泛指实现了 IController 接口的类型
激活过程中的核心类型
- Controller
Asp.net mvc ** 中的控制器 类都是抽象类 Controller 的子类,Controller** 类又是抽象类 ControllerBase 的子类,其实现了 IController 接口,该接口定义在该接口的定义如下所示:
public interface IController
{
void Execute(RequestContext requestContext);
}
当成功创建 Controller 实例后,后续的操作便是通过该方法来实现的,要注意的是该方法是通过 同步(Synchronization) 的方式实现的。
在 System.Web.Mvc.Async 命名空间下,定义有一个 IAsyncController 接口,该接口实现了 IController 接口,该接口的定义如下:
public interface IAsyncController : IController
{
IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
void EndExecute(IAsyncResult asyncResult);
}
实现了该接口的 Controller 实例在执行核心的 Execute
方法时可能是通过异步的方法执行的,之所以说可能是因为,Execute
方法是以同步还是异步的方式执行还取决于 Controller 类的一个 Boolean 类型的属性:DisableAsyncSupport
,该属性指示是否禁用控制器的异步执行(Execute 方法),默认为 False,即默认启用控制器的异步执行。
在 Execute
方法的异步版本 BeginExecute
中会判断 DisableAsyncSupport 的值,如果该值为 True ,则会调用同步版本的 Execute
,然后返回,部分代码如下所示:
Action action2 = null;
if (this.DisableAsyncSupport)
{
if (action2 == null)
{
action2 = delegate {
this.Execute(requestContext);
};
}
Action action = action2;
return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeTag);
}
抽象的 Controller 实现了许多的接口,如下所示:
public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IController, IAsyncManagerContainer
实现的接口(基类)体来分,可以分为三类:
-
与 Controller 相关的接口,有
ControllerBase
、IController
、IAsyncController
-
过滤器(Filter) 接口
IActioinFilter
IAuthenticationFilter
IAuthorizationFilter
IExceptionFilter
IResultFilter
-
异步操作相关的接口,有
IAysncManagerContainer
,为异步操作执行参数传递、操作计数和超时控制
除去上面的三类,还有一个 IDisposeable
接口,**Asp.net mvc ** 的控制器激活系统会在 Controller 的执行完成后,调用该方法进行资源的释放。
Controller 中的方法
Controller 中定义的方法,大体上可以分为三类:
- 上面定义的接口的实现方法
- 返回结果为
ActionResult
的方法 - 其它 方法
定义的方法中的大多数属于上面的前两类。下面选择几个关键方法进行说明:
- void Initialize(RequestContext requestContext)
不论是同步执行还是异步执行,在操作执行之前都会先调用该方法,在该方法中执行了两步操作,第一是将当前请求的 RequestContext 和 当前的 Controller 实例封装为一个 ControllerContext 对象,第二部根据当前请求的 RequestContext 生成一个 UrlHelper 对象。
- IActionInvoker CreateActionInvoker()
所有的 Action 方法的执行调用都是通过 IActionInvoker 接口的bool InvokeAction(ControllerContext context,string actionName)
来实现的,该方法的作用就是创建一个 IActionInvoker 类型的实例,其实现如下:
protected virtual IActionInvoker CreateActionInvoker()
{
IAsyncActionInvokerFactory service = this.Resolver.GetService<IAsyncActionInvokerFactory>();
if (service != null)
{
return service.CreateInstance();
}
IActionInvokerFactory factory2 = this.Resolver.GetService<IActionInvokerFactory>();
if (factory2 != null)
{
return factory2.CreateInstance();
}
return (this.Resolver.GetService<IAsyncActionInvoker>() ?? (this.Resolver.GetService<IActionInvoker>() ?? new AsyncControllerActionInvoker()));
}
从上面的代码可以看出,IActionInvoker 实例的创建采用了 IOC 的思想和工厂模式的实现,可以很容易的使用第三方的 IOC 容器进行扩展,首先通过 Asp.net mvc 内部的 IOC 容器 Resolver 是否有已注入的 IAsyncActionInvokerFactory 类型,如果存在,则调用该类型的 CreateInstance()
方法创建一个 IAsyncActionInvoker 类型的实例返回。如果不存在,则继续判断容器内是否有已注入的 IActionInvokerFactory 类型,如果存在,则调用该类型的 CreateInstance()
方法创建一个 IActionInvoker 类型的实例返回。
如果上面两种的 ActionInvoerFactory 都不存在,则在容器内判断是否存在已注入的 IAsyncActionInvoer 类型,如果存在则返回该类型的实例,否则判断容器内是否存在已注入的 IActionInvoker 类型,如果存在则返回该类型的实例,否则返回一个 AsyncControllerActionInvoker 类型的实例
IAsyncActionInvoer 接口实现了 IActionInvoker 接口
- string GetActionName(RouteData data)</kbd
根据给定的 RouteData 获取当前请求的 Action 的名称
4. void ExecuteCore()
当 Controller 执行同步的 Execute
方法时,方法内部会调用该方法,该方法中主要实现了两步操作,第一,调用 GetActionName
方法获取当前请求的 Action 的名称,第二步,根据上一步获取的 ActionName 调用 IActionInvoker 的 InvokeAction
方法
Controller 中的属性(部分)
public IActionInvoker ActionInvoker { get; set; }
public AsyncManager AsyncManager { get; }
public IDependencyResolver Resolver { get; set; }//内部IOC容器
protected virtual bool DisableAsyncSupport { get; }
public HttpContextBase HttpContext { get; }
public HttpRequestBase Request { get; }//HttpContext.Request 封装
public HttpResponseBase Response { get; }//HttpContext.Response 封装
public RouteData RouteData { get; }
public HttpServerUtilityBase Server { get; }
public HttpSessionStateBase Session { get; }
public UrlHelper Url { get; set; }
public IPrincipal User { get; }// 与 HttpContext.User||Thread.CurrentPrincipal 相同
public ViewEngineCollection ViewEngineCollection { get; set; }
public ControllerContext ControllerContext { get; set; }//Controller 与 Request d 封装
// Controller 向 View 传递参数
public TempDataDictionary TempData { get; set; }
public object ViewBag { [return: Dynamic] get; }
public ViewDataDictionary ViewData { get; set; }
- ControllerFactory
ControllerFactory 泛指实现了 IControllerFactory 接口的类型,该接口的定义如下:
public interface IControllerFactory
{
IController CreateController(RequestContext requestContext, string controllerName);
SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
void ReleaseController(IController controller);
}
}
CreateController(RequestContext requestContext, string controllerName)
根据 RequestContext 和 Controller 的名称创建 IController 实例
ReleaseController(IController controller)
调用指定 IController 实例的 Disposable
方法释放所占的资源
**SessionStateBehavior ** 表示请求处理过程中会话状态的支持模式,是一个枚举值,具有如下的四个选项:
选项值 | 说明 |
---|---|
Default | 使用默认的 Asp.net 逻辑来来确定请求的回话状态行为 |
Required | 为请求启用完全的 读/写 会话状态行为 |
ReadOnly | 为请求启用只读 的会话状态行为 |
Disabled | 禁用会话状态行为 |
对于默认选项,Asp.net 会根据映射的 **IHttpHandler** 是否实现了某些接口来决定其请求处理过程中的会话状态行为,在命名空间 **System.Web.SessionState** 下定义了 `IRequiresSessionState` 和 `IReadOnlySessionState` 两个接口,后者实现了前者,这两个接口均为 **标记接口**,即接口中未定义任何的内容。例如,**MVCHandler** 便实现了 **IRequiresSessionState** 接口,因此,默认情况下,在请求处理过程中,对会话状态具有 读/写 的行为。 在 **Asp.net mvc** 中 **DefaultControllerFactory** 提供了 **IControllerFactory** 接口的默认实现,默认情况下都是使用该类来创建 **IController** 类型的实例的,在没有注册 **IOC** 容器的情况下,会根据 **Controller** 的类型通过反射的方式创建 **IController** 类型的实例。 可以在 Controller 上使用 **SessionStateAttribute** 属性对该Controller 的 **SessionStateBehavior** 进行控制。
- ControllerBuilder
ControllerBuilder 用于对创建 IController 实例的 IControllerFactory 类型进行注册,其定义如下:
public class ControllerBuilder
{
public HashSet<string> DefaultNamespaces { get; }
public IControllerFactory GetControllerFactory();//获取当前的 ControllerFactory
//设置 ControllerFactory
public void SetControllerFactory(IControllerFactory controllerFactory);
public void SetControllerFactory(Type controllerFactoryType);
Controller 的默认激活机制
在接受到 IIS 分发的 Http 请求后,Asp.net 会利用一个 URLRoutingModule 类型的拦截器对请求进行拦截处理,在该类型的 void Init(HttpApplication application)
中会对 Asp.net ** 管道事件中的 PostResolveRequestCache
事件进行注册,在对该事件的处理中,会对当前的请求进行解析和封装,解析会产生一个 IRouteData 类型的实例,其中主要存放与该次请求匹配的路由信息,该类型具有一个 IRouteHandler 类型的属性,调用该属性的 IHttpHandler GetHttpHandler(RequestContext context)
方法,便可获取到一个对当前请求进行处理的 HttpHandler,如果当前应用为 Asp.Net Mvc 应用,则返回的便为 MvcHandler,其具有一个 void ProcessRequest(HttpContext context)
,Controller** 的激活便是在该方法中进行的。
IHttpHandler 中的
ProcessRequest(HttpContext)
方法是以同步的方式执行的,在 Asp.net ** 中还定义有一个异步的实现 IAsycHttpHandler,该接口实现了 IHttpHandler,MVCHandler** 同时实现了这两个接口,提供了同步和异步的实现。
获取 Controller 的整体流程如下:
1. 调用 RouteData 的 GetRequiredString(string name)
方法获取请求的 Controller的名称,参数的名称为 “controller“。
2. 调用 ControllerBuilder 的静态方法 IControllerFactory GetControllerFactory()
方法,获取当前注册的 ControllerFactory 类型实例。
3. 调用 ControllerFactory 实例的 CreateController(RequestContext context,string controllerName)
方法创建 Controller 类型的实例并返回。
在默认情况下,ControllerFactory 内部是通过反射的方式创建 Controller 类型的实例,因此,除了控制器的名称外,还需要知道控制器的具体类型。
通过从 RouteData 中获取得到的 命名空间 + 控制器的名称 + Controller 后缀组成的不一定是真正的控制器的真正类型,因为从中获取的控制器的名称是不区分大小写的,并且命名空间中可能会包括通配符,因此不能通过这种方式得到的名称去创建 Controller 类型的实例。
默认的 DefaultControllerFactory 中,会首先调用 BuildManager 的静态方法 GetReferencesAssembilies
方法获取应用引用的所用程序集,然后通过反射的方式获取所用实现了 IController 的类型,然后根据控制器的名称和命名空间去选择相应的类型。然后根据获取的类型在调用 CreateController
方法创建 Controller 类型的实例。
在某种情况下,可能存在不同的命名空间下存在名称相同的 IController 类型,这时可以提升某一命名空间匹配时的优先级的方式进行解决,在 **Asp.Net Mvc ** 中有两种方式来实现,将要优先匹配的命名空间名称添加到 ControllerBuilder 类型的 DefaultNameSpaces 中,第二种方式是在进行路由注册时,将其添加到所注册路由的 namespaces 列表中,这种方式添加的命名空间,最终会被存储在 RouteData 的 DataTokens 属性的 RouteValueDictory 中,其对应的 Key 为 Namespaces,两种方式中,后者具有更高的优先级。
Controller 类型的缓存
在上面说过,在获取 Controller 类型时,是通过反射的方式去查找应用引用程序集中所有实现了 IController 的类型,这样是非常耗费性能的,因此,**Asp.net mvc ** 在第一次解析完成后,会对解析出的结果进行缓存,下一次便会先从缓存中去获取,如果获取失败在通过反射的方式获取,然后将其添加到缓存中。这种缓存是通过文件的方式实现的,该文件是一个名称为 MvcControllerCache.xml 的 xml 文件,其存储的路径如下:
该文件的内容大致如下:
<?xml version="1.0" encoding="utf-8"?>
<typeCache lastModified="2017/9/18 10:25:51" mvcVersionId="cc73190b-ab9d-435c-8315-10ff295c572a">
<assembly name="Orders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<module versionId="42e93909-1c42-4e8e-8270-442e4157d8b3">
<type>Orders.Controllers.OrderController</type>
</module>
</assembly>
</typeCache>
从中可以看出,主要对包括 Controller 的具体类型 和其所在的程序集。当获取 Controller 类型时,会首先从缓存文件通过反序列化的方式产生一个 List<Type>,然后根据控制器名称从其中获取 控制器的类型,如果不存在的情况下才会通过反射的方式去获取,然后将其存入缓存。