前文简介:我们抽象类路由规则的对象,RouteBase 是路由对象的抽象基类,ASP.NET 的路由系统中有唯一一个从 RouteBase 继承的路由对象,那就是 Route 类型了。我们注册了路由对象 Route,UrlRoutingModule 截获请求,把当前请求的 Url 地址和 RouteTable 路由表中注册的路由对象一个一个的比较,如果没有找到就返回 Null,请求就到此终止了。如果有匹配的路由对象,选择第一个匹配的 Route 对象,并且根据该 Route 对象,生成了路由数据 RouteData 对象,本对象是为了封装成 RequestContext 对象,RequestContext 对象封装了 RouteData 和 HttpContext 对象。
其实 RequestContext 对象也是作为参数使用的,我们根据 Route 对象获得了 RouteData,该对象有一个 RouteHandler 属性,这个属性的值在 ASP.NET MVC 中是 MvcRouteHandler。RouteHandler 主要的作用是提供用于处理最终请求的 HttpHandler 对象,代码如下:
- IHttpHandler = RouteData.RouteHandler.GetHttpHandler(requestContext);
- HttpContext.Remap(IHttpHandler);
上文中我们获取到的 RequestContext,将此作为参数调用 RouteHandler 的 GetHttpHandler 方法,我们获得了 IHttpHandler 对象,我们需要把请求交给这个 HttpHandler 来处理,通过 HttpContext.Remap(Handler) 实现请求的交接,这个 Handler 在 ASP.NET MVC 中就是 MvcHandler,我们既然获得了 HttpHandler 对象,也实现了请求的交接,下一步该做什么呢?
一、概览
我们获得了用于处理请求 HttpHandler 对象,下一步最重要的任务是把 Controller 对象找到,RouteData 对象的 Values 属性的 Controller 键和 Action 键的值只是一个字符串的结果,我们要通过这个名称找到具体的 Controller 类型实例,才能执行 Action 方法,给客户想要的应答。这篇文章就是 Controller 的激活方法的详解。
ASP.NET MVC 系统真的很庞大,也很复杂,如果我们一上来就瀑布式的说,就像流水账一样,估计也不太容易说明白。为了便于理解,我们人为的把 ASP.NET MVC 这么庞大的系统拆分成相互独立,又有一定联系的多个子系统,然后我们各个击破,理解起来也就简单了,最后在把各个部分整合在一起,理解就全面了。今天我们要说的这一部分暂时叫做 Controller 激活系统吧,激活系统有两个含义,一是我们要找到我们需要的 Controller 对象,并实例化;二是我们药缓存他,并要执行它。
我们先来看看 MvcHandler 的源码吧,有助于我们的理解,代码如下:
- /// <summary>Selects the controller that will handle an HTTP request.</summary>
- public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
- {
- private struct ProcessRequestState
- {
- internal IAsyncController AsyncController;
- internal IControllerFactory Factory;
- internal RequestContext RequestContext;
- internal void ReleaseController()
- {
- this.Factory.ReleaseController(this.AsyncController);
- }
- }
- private static readonly object _processRequestTag = new object();
- internal static readonly string MvcVersion = MvcHandler.GetMvcVersionString();
- /// <summary>Contains the header name of the ASP.NET MVC version.</summary>
- public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version";
- private ControllerBuilder _controllerBuilder;
- internal ControllerBuilder ControllerBuilder
- {
- get
- {
- if (this._controllerBuilder == null)
- {
- this._controllerBuilder = ControllerBuilder.Current;
- }
- return this._controllerBuilder;
- }
- set
- {
- this._controllerBuilder = value;
- }
- }
- /// <summary>Gets or sets a value that indicates whether the MVC response header is disabled.</summary>
- /// <returns>true if the MVC response header is disabled; otherwise, false.</returns>
- public static bool DisableMvcResponseHeader
- {
- get;
- set;
- }
- /// <summary>Gets a value that indicates whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.</summary>
- /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns>
- protected virtual bool IsReusable
- {
- get
- {
- return false;
- }
- }
- /// <summary>Gets the request context.</summary>
- /// <returns>The request context.</returns>
- public RequestContext RequestContext
- {
- get;
- private set;
- }
- /// <summary>Gets a value that indicates whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.</summary>
- /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns>
- bool IHttpHandler.IsReusable
- {
- get
- {
- return this.IsReusable;
- }
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.MvcHandler" /> class.</summary>
- /// <param name="requestContext">The request context.</param>
- /// <exception cref="T:System.ArgumentNullException">The <paramref name="requestContext" /> parameter is null.</exception>
- public MvcHandler(RequestContext requestContext)
- {
- if (requestContext == null)
- {
- throw new ArgumentNullException("requestContext");
- }
- this.RequestContext = requestContext;
- }
- /// <summary>Adds the version header by using the specified HTTP context.</summary>
- /// <param name="httpContext">The HTTP context.</param>
- protected internal virtual void AddVersionHeader(HttpContextBase httpContext)
- {
- if (!MvcHandler.DisableMvcResponseHeader)
- {
- httpContext.Response.AppendHeader(MvcHandler.MvcVersionHeaderName, MvcHandler.MvcVersion);
- }
- }
- /// <summary>Called by ASP.NET to begin asynchronous request processing.</summary>
- /// <returns>The status of the asynchronous call.</returns>
- /// <param name="httpContext">The HTTP context.</param>
- /// <param name="callback">The asynchronous callback method.</param>
- /// <param name="state">The state of the asynchronous object.</param>
- protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
- {
- HttpContextBase httpContext2 = new HttpContextWrapper(httpContext);
- return this.BeginProcessRequest(httpContext2, callback, state);
- }
- /// <summary>Called by ASP.NET to begin asynchronous request processing using the base HTTP context.</summary>
- /// <returns>The status of the asynchronous call.</returns>
- /// <param name="httpContext">The HTTP context.</param>
- /// <param name="callback">The asynchronous callback method.</param>
- /// <param name="state">The state of the asynchronous object.</param>
- protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
- {
- IController controller;
- IControllerFactory factory;
- this.ProcessRequestInit(httpContext, out controller, out factory);
- IAsyncController asyncController = controller as IAsyncController;
- if (asyncController != null)
- {
- BeginInvokeDelegate<MvcHandler.ProcessRequestState> beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState, MvcHandler.ProcessRequestState innerState)
- {
- IAsyncResult result;
- try
- {
- result = innerState.AsyncController.BeginExecute(innerState.RequestContext, asyncCallback, asyncState);
- }
- catch
- {
- innerState.ReleaseController();
- throw;
- }
- return result;
- };
- EndInvokeVoidDelegate<MvcHandler.ProcessRequestState> endDelegate = delegate(IAsyncResult asyncResult, MvcHandler.ProcessRequestState innerState)
- {
- try
- {
- innerState.AsyncController.EndExecute(asyncResult);
- }
- finally
- {
- innerState.ReleaseController();
- }
- };
- MvcHandler.ProcessRequestState invokeState = new MvcHandler.ProcessRequestState
- {
- AsyncController = asyncController,
- Factory = factory,
- RequestContext = this.RequestContext
- };
- SynchronizationContext synchronizationContext = SynchronizationContextUtil.GetSynchronizationContext();
- return AsyncResultWrapper.Begin<MvcHandler.ProcessRequestState>(callback, state, beginDelegate, endDelegate, invokeState, MvcHandler._processRequestTag, -1, synchronizationContext);
- }
- Action action = delegate
- {
- try
- {
- controller.Execute(this.RequestContext);
- }
- finally
- {
- factory.ReleaseController(controller);
- }
- };
- return AsyncResultWrapper.BeginSynchronous(callback, state, action, MvcHandler._processRequestTag);
- }
- /// <summary>Called by ASP.NET when asynchronous request processing has ended.</summary>
- /// <param name="asyncResult">The asynchronous result.</param>
- protected internal virtual void EndProcessRequest(IAsyncResult asyncResult)
- {
- AsyncResultWrapper.End(asyncResult, MvcHandler._processRequestTag);
- }
- private static string GetMvcVersionString()
- {
- return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2);
- }
- /// <summary>Processes the request by using the specified HTTP request context.</summary>
- /// <param name="httpContext">The HTTP context.</param>
- protected virtual void ProcessRequest(HttpContext httpContext)
- {
- HttpContextBase httpContext2 = new HttpContextWrapper(httpContext);
- this.ProcessRequest(httpContext2);
- }
- /// <summary>Processes the request by using the specified base HTTP request context.</summary>
- /// <param name="httpContext">The HTTP context.</param>
- protected internal virtual void ProcessRequest(HttpContextBase httpContext)
- {
- IController controller;
- IControllerFactory controllerFactory;
- this.ProcessRequestInit(httpContext, out controller, out controllerFactory);
- try
- {
- controller.Execute(this.RequestContext);
- }
- finally
- {
- controllerFactory.ReleaseController(controller);
- }
- }
- private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
- {
- HttpContext current = HttpContext.Current;
- if (current != null && ValidationUtility.IsValidationEnabled(current) == true)
- {
- ValidationUtility.EnableDynamicValidation(current);
- }
- this.AddVersionHeader(httpContext);
- this.RemoveOptionalRoutingParameters();
- string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
- factory = this.ControllerBuilder.GetControllerFactory();
- controller = factory.CreateController(this.RequestContext, requiredString);
- if (controller == null)
- {
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[]
- {
- factory.GetType(),
- requiredString
- }));
- }
- }
- private void RemoveOptionalRoutingParameters()
- {
- RouteValueDictionary values = this.RequestContext.RouteData.Values;
- values.RemoveFromDictionary((KeyValuePair<string, object> entry) => entry.Value == UrlParameter.Optional);
- }
- /// <summary>Enables processing of HTTP Web requests by a custom HTTP handler that implements the <see cref="T:System.Web.IHttpHandler" /> interface.</summary>
- /// <param name="httpContext">An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) that are used to service HTTP requests.</param>
- void IHttpHandler.ProcessRequest(HttpContext httpContext)
- {
- this.ProcessRequest(httpContext);
- }
- /// <summary>Called by ASP.NET to begin asynchronous request processing using the base HTTP context.</summary>
- /// <returns>The status of the asynchronous call.</returns>
- /// <param name="context">The HTTP context.</param>
- /// <param name="cb">The asynchronous callback method.</param>
- /// <param name="extraData">The data.</param>
- IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
- {
- return this.BeginProcessRequest(context, cb, extraData);
- }
- /// <summary>Called by ASP.NET when asynchronous request processing has ended.</summary>
- /// <param name="result">The asynchronous result.</param>
- void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
- {
- this.EndProcessRequest(result);
- }
- }
MvcHandler 里面这个两个方法定义了 Controller 激活系统实现的骨架:
- /// <summary>Processes the request by using the specified base HTTP request context.</summary>
- /// <param name="httpContext">The HTTP context.</param>
- protected internal virtual void ProcessRequest(HttpContextBase httpContext)
- {
- IController controller;
- IControllerFactory controllerFactory;
- this.ProcessRequestInit(httpContext, out controller, out controllerFactory);
- try
- {
- controller.Execute(this.RequestContext);
- }
- finally
- {
- controllerFactory.ReleaseController(controller);
- }
- }
- private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
- {
- HttpContext current = HttpContext.Current;
- if (current != null && ValidationUtility.IsValidationEnabled(current) == true)
- {
- ValidationUtility.EnableDynamicValidation(current);
- }
- this.AddVersionHeader(httpContext);
- this.RemoveOptionalRoutingParameters();
- string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
- factory = this.ControllerBuilder.GetControllerFactory();
- controller = factory.CreateController(this.RequestContext, requiredString);
- if (controller == null)
- {
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[]
- {
- factory.GetType(),
- requiredString
- }));
- }
- }
代码不是很难,大家一看就能看懂,下一节好好的介绍一些 Controller 激活系统中所涉及到的各个对象,具体的解析规则要到下一篇文章了,否则文章就太长了。
二、Controller 对象激活详述
ASP.NET MVC 中,我们人为分解的当前系统叫:Controller 激活系统,其实这个激活系统里面包含的对象也不是很多,主要有我们要实例化的 Controller 对象,管理 Controller 的 ControllerFactory 对象,管理 ControllerFactory 的 ControllerBuilder 对象,这三个主要对象构成了我们的激活系统。
1、我们先看看 Controller 类型的定义吧
我们所说的 Controller 其实是指实现了 IController 接口的某个类型实例。Controller 是一个可以执行的对象,它的执行体现在对 Execute 方法上的调用。一说到执行我们是不是就会想到同步执行和异步执行呢,既然 Controller 是一个可以执行的对象,他会不会也具有同步和异步的执行呢,答案是肯定的,我们先来看看两个接口吧:
- public interface IController
- {
- /// <summary>Executes the specified request context.</summary>
- /// <param name="requestContext">The request context.</param>
- void Execute(RequestContext requestContext);
- }
这个就是同步版的接口,该接口的命名空间是:System.Web.Mvc,当目标 Controller 对象被成功激活后,对请求的后续处理和最终响应都通过执行者 Execute 方法来实现。我们再看看异步版的接口定义吧:
- public interface IAsyncController: IController {
- /// <summary>Executes the specified request context.</summary>
- /// <returns>The status of the asynchronous operation.</returns>
- /// <param name="requestContext">The request context.</param>
- /// <param name="callback">The asynchronous callback method.</param>
- /// <param name="state">The state.</param>
- IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
- /// <summary>Ends the asynchronous operation.</summary>
- /// <param name="asyncResult">The asynchronous result.</param>
- void EndExecute(IAsyncResult asyncResult);
- }
异步版接口定义的命名空间是:System.Web.Mvc.Async,我们看到 IAsyncController 是实现了 IController 的接口,Controller 的异步执行是通过调用 BeginExecute 和 EndExecute 方法来实现的。我们通过 Visual Studio 创建的 ASP.NET MVC 项目,我们自定义的 Controller 实现的基类是 Controller 类型,不是直接实现以上接口的。我们看看 Controller 类型的定义吧:
- public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IController, IAsyncManagerContainer
- {
- //代码省略
- }
Controller 是抽象类型,实现了很多接口,因为该类的代码太多,所以省略了部分内部代码,这个里面牵扯到另外一个类,就是 ControllerBase,
- /// <summary>Represents the base class for all MVC controllers.</summary>
- public abstract class ControllerBase : IController
- {
- private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
- private DynamicViewDataDictionary _dynamicViewDataDictionary;
- private TempDataDictionary _tempDataDictionary;
- private bool _validateRequest = true;
- private IValueProvider _valueProvider;
- private ViewDataDictionary _viewDataDictionary;
- /// <summary>Gets or sets the controller context.</summary>
- /// <returns>The controller context.</returns>
- public ControllerContext ControllerContext
- {
- get;
- set;
- }
- /// <summary>Gets or sets the dictionary for temporary data.</summary>
- /// <returns>The dictionary for temporary data.</returns>
- public TempDataDictionary TempData
- {
- get
- {
- if (this.ControllerContext != null && this.ControllerContext.IsChildAction)
- {
- return this.ControllerContext.ParentActionViewContext.TempData;
- }
- if (this._tempDataDictionary == null)
- {
- this._tempDataDictionary = new TempDataDictionary();
- }
- return this._tempDataDictionary;
- }
- set
- {
- this._tempDataDictionary = value;
- }
- }
- /// <summary>Gets or sets a value that indicates whether request validation is enabled for this request.</summary>
- /// <returns>true if request validation is enabled for this request; otherwise, false. The default is true.</returns>
- public bool ValidateRequest
- {
- get
- {
- return this._validateRequest;
- }
- set
- {
- this._validateRequest = value;
- }
- }
- /// <summary>Gets or sets the value provider for the controller.</summary>
- /// <returns>The value provider for the controller.</returns>
- public IValueProvider ValueProvider
- {
- get
- {
- if (this._valueProvider == null)
- {
- this._valueProvider = ValueProviderFactories.Factories.GetValueProvider(this.ControllerContext);
- }
- return this._valueProvider;
- }
- set
- {
- this._valueProvider = value;
- }
- }
- /// <summary>Gets the dynamic view data dictionary.</summary>
- /// <returns>The dynamic view data dictionary.</returns>
- [Dynamic]
- public dynamic ViewBag
- {
- [return: Dynamic]
- get
- {
- if (this._dynamicViewDataDictionary == null)
- {
- this._dynamicViewDataDictionary = new DynamicViewDataDictionary(() => this.ViewData);
- }
- return this._dynamicViewDataDictionary;
- }
- }
- /// <summary>Gets or sets the dictionary for view data.</summary>
- /// <returns>The dictionary for the view data.</returns>
- public ViewDataDictionary ViewData
- {
- get
- {
- if (this._viewDataDictionary == null)
- {
- this._viewDataDictionary = new ViewDtaDictionary();
- }
- return this._viewDataDictionary;
- }
- set
- {
- this._viewDataDictionary = value;
- }
- }
- /// <summary>Executes the specified request context.</summary>
- /// <param name="requestContext">The request context.</param>
- /// <exception cref="T:System.ArgumentNullException">The <paramref name="requestContext" /> parameter is null.</exception>
- protected virtual void Execute(RequestContext requestContext)
- {
- if (requestContext == null)
- {
- throw new ArgumentNullException("requestContext");
- }
- if (requestContext.HttpContext == null)
- {
- throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
- }
- this.VerifyExecuteCalledOnce();
- this.Initialize(requestContext);
- using (ScopeStorage.CreateTransientScope())
- {
- this.ExecuteCore();
- }
- }
- /// <summary>Executes the request.</summary>
- protected abstract void ExecuteCore();
- /// <summary>Initializes the specified request context.</summary>
- /// <param name="requestContext">The request context.</param>
- protected virtual void Initialize(RequestContext requestContext)
- {
- this.ControllerContext = new ControllerContext(requestContext, this);
- }
- internal void VerifyExecuteCalledOnce()
- {
- if (!this._executeWasCalledGate.TryEnter())
- {
- string message = string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, new object[]
- {
- base.GetType()
- });
- throw new InvalidOperationException(message);
- }
- }
- /// <summary>Executes the specified request context.</summary>
- /// <param name="requestContext">The request context.</param>
- void IController.Execute(RequestContext requestContext)
- {
- this.Execute(requestContext);
- }
- }
ControllerBase 实现了 IController 接口,但是没有实现 IAsyncController 接口,说明 ControllerBase 是一个同步的基类,之所以单独又增加 ControllerBase,个人认为它定义了方法调用的扩展点。ControllerBase 是一个抽象类,通过显示的实现了 IController 的 Execute 方法,在这个显示实现的方法 IController.Execute 方法里面又调用了受保护的虚方法 Execute,这个受保护的虚方法 Execute 又在内部调用了抽象方法 ExecuteCore,作为 ControllerBase 的继承者,必须通过实现对抽象方法 ExecuteCore 来完成对 Controller 的执行。这个过程就是方法调用的扩展点,而且 ControllerBase 也抽象所有 Controller 中都会用到的一些共同的属性,如 TempData,ControllerContext,ValueProvider,ViewBag,ViewData 等,大家可以细细研究,这里就不细说了,都很简单。
在 ControllerBase 里面有一个初始化的方法,这个方法的作用是根据当前 Controller 对象和当前请求上下文 RequestContext 创建 ControllerContext 对象,在请求后续处理的时候很多地方会用到 ControllerContext。
- /// <summary>Initializes the specified request context.</summary>
- /// <param name="requestContext">The request context.</param>
- protected virtual void Initialize(RequestContext requestContext)
- {
- this.ControllerContext = new ControllerContext(requestContext, this);
- }
此方法是虚方法,我们可以扩展他,这个方法的调用点在受保护的虚方法 Exuecute 中,在调用 ExecuteCore 之前调用了该方法。
我们说了接口 IController,IAsyncController,也说了抽象类 ControllerBase,我们最后对我们要经常使用的基类做一下简述,我们真正在项目中使用的 Controller 的基类型是 Controller 类型,Controller 实现了 IController 接口和 IAsyncController 接口,说明我们自己的 Controller 是可以同步执行,也可以异步执行的,同时实现了 IDisposable 接口,说明 Controller 类型是需要释放的,谁来释放激活的 Controller 的类型呢,那就是我们接下来要说的一个对象 ControllerFactory,这里不多说,下面会解释清楚的。
最后我们需要说明的,虽然 Controller 可以同步执行,也可以异步执行,但是是同步还是异步是靠 DisableAsyncSupport 属性控制的,返回的是布尔值,默认情况是 false,意思是指支持异步执行,以下代码说明实际情况。当 BeginExecute 方法执行的时候,它会根据该属性的值决定调用 Execute 同步执行 Controller,还是调用 BeginExecuteCore/EndExecuteCore 方法异步执行 Controller,换句话说,如果总是希望 Controller 同步执行,那就要把 DisableAsyncSupport 属性设置为 true。
- protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
- {
- if (this.DisableAsyncSupport)
- {
- Action action = delegate
- {
- this.Execute(requestContext);
- };
- return AsyncResultWrapper.BeginSynchronous(callback, state, action, Controller._executeTag);
- }
- if (requestContext == null)
- {
- throw new ArgumentNullException("requestContext");
- }
- 16 this.Initialize(requestContext);
- BeginInvokeDelegate<Controller> beginDelegate = (AsyncCallback asyncCallback, object callbackState, Controller controller) => controller.BeginExecuteCore(asyncCallback, callbackState);
- EndInvokeVoidDelegate<Controller> endDelegate = delegate(IAsyncResult asyncResult, Controller controller)
- {
- controller.EndExecuteCore(asyncResult);
- };
- return AsyncResultWrapper.Begin<Controller>(callback, state, beginDelegate, endDelegate, this, Controller._executeTag, -1, null);
- }
2、ControllerContext
在 ASP.NET MVC 中我们会遇到很多上下文(Context)的类型,比如:RequestContext,它是对 HttpContext 和 RouteData 的封装,今天我们看看 ControllerContext,就是指 Controller 的上下文,再说明白一点就是 Controller 在执行过程中对需要用到的一些数据的封装对象,它类似一个 Facade,里面包含了一些在 Controller 执行时所要用的一些对象,必须表示请求的 HttpContextBase 对象。这个类型没什么可说的,很简单,我把反编译的代码也都贴出来了,大家可以自己看。
- /// <summary>Encapsulates information about an HTTP request that matches specified <see cref="T:System.Web.Routing.RouteBase" /> and <see cref="T:System.Web.Mvc.ControllerBase" /> instances.</summary>
- public class ControllerContext
- {
- private sealed class EmptyHttpContext : HttpContextBase
- {
- }
- internal const string ParentActionViewContextToken = "ParentActionViewContext";
- private HttpContextBase _httpContext;
- private RequestContext _requestContext;
- private RouteData _routeData;
- /// <summary>Gets or sets the controller.</summary>
- /// <returns>The controller.</returns>
- public virtual ControllerBase Controller
- {
- get;
- set;
- }
- /// <summary>Gets the display mode.</summary>
- /// <returns>The display mode.</returns>
- public IDisplayMode DisplayMode
- {
- get
- {
- return DisplayModeProvider.GetDisplayMode(this.HttpContext);
- }
- set
- {
- DisplayModeProvider.SetDisplayMode(this.HttpContext, value);
- }
- }
- /// <summary>Gets or sets the HTTP context.</summary>
- /// <returns>The HTTP context.</returns>
- public virtual HttpContextBase HttpContext
- {
- get
- {
- if (this._httpContext == null)
- {
- this._httpContext = ((this._requestContext != null) ? this._requestContext.HttpContext : new ControllerContext.EmptyHttpContext());
- }
- return this._httpContext;
- }
- set
- {
- this._httpContext = value;
- }
- }
- /// <summary>Gets a value that indicates whether the associated action method is a child action.</summary>
- /// <returns>true if the associated action method is a child action; otherwise, false.</returns>
- public virtual bool IsChildAction
- {
- get
- {
- RouteData routeData = this.RouteData;
- return routeData != null && routeData.DataTokens.ContainsKey("ParentActionViewContext");
- }
- }
- /// <summary>Gets an object that contains the view context information for the parent action method.</summary>
- /// <returns>An object that contains the view context information for the parent action method.</returns>
- public ViewContext ParentActionViewContext
- {
- get
- {
- return this.RouteData.DataTokens["ParentActionViewContext"] as ViewContext;
- }
- }
- /// <summary>Gets or sets the request context.</summary>
- /// <returns>The request context.</returns>
- public RequestContext RequestContext
- {
- get
- {
- if (this._requestContext == null)
- {
- HttpContextBase httpContext = this.HttpContext ?? new ControllerContext.EmptyHttpContext();
- RouteData routeData = this.RouteData ?? new RouteData();
- this._requestContext = new RequestContext(httpContext, routeData);
- }
- return this._requestContext;
- }
- set
- {
- this._requestContext = value;
- }
- }
- /// <summary>Gets or sets the URL route data.</summary>
- /// <returns>The URL route data.</returns>
- public virtual RouteData RouteData
- {
- get
- {
- if (this._routeData == null)
- {
- this._routeData = ((this._requestContext != null) ? this._requestContext.RouteData : new RouteData());
- }
- return this._routeData;
- }
- set
- {
- this._routeData = value;
- }
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class.</summary>
- public ControllerContext()
- {
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class by using the specified controller context.</summary>
- /// <param name="controllerContext">The controller context.</param>
- /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext" /> parameter is null.</exception>
- protected ControllerContext(ControllerContext controllerContext)
- {
- if (controllerContext == null)
- {
- throw new ArgumentNullException("controllerContext");
- }
- this.Controller = controllerContext.Controller;
- this.RequestContext = controllerContext.RequestContext;
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class by using the specified HTTP context, URL route data, and controller.</summary>
- /// <param name="httpContext">The HTTP context.</param>
- /// <param name="routeData">The route data.</param>
- /// <param name="controller">The controller.</param>
- public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller) : this(new RequestContext(httpContext, routeData), controller)
- {
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class by using the specified request context and controller.</summary>
- /// <param name="requestContext">The request context.</param>
- /// <param name="controller">The controller.</param>
- /// <exception cref="T:System.ArgumentNullException">One or both parameters are null.</exception>
- public ControllerContext(RequestContext requestContext, ControllerBase controller)
- {
- if (requestContext == null)
- {
- throw new ArgumentNullException("requestContext");
- }
- if (controller == null)
- {
- throw new ArgumentNullException("controller");
- }
- this.RequestContext = requestContext;
- this.Controller = controller;
- }
- }
3、ControllerFactory
我们的 Controller 对象有了,为了解耦和可扩展性,ASP.NET MVC 框架中不可能直接 new 一个 Controller 的实例对象吧,我们在 MvcHandler 的代码中也看到了是基于接口编程的,所以需要提供一种机制,来提供我们要得到的 Controller 对象。既然不能直接 new 一个 Controller 出来,那我们就封装 new 的过程,用工厂来代替吧。对了,就是工厂,这个工厂就是 ControllerFactory,从名字上可以看出它的意思,创建 Controller 对象的工厂。ASP.NET MVC 是可以扩展的,可以自定义的,我们的 ControllerFactory 工厂也不例外,所以我们就产生了 ControllerFactory 工厂的接口,接口的名字是 IControllerFactory,所有的 ControllerFactory 都必须实现该接口。接口定义如下:
- /// <summary>Defines the methods that are required for a controller factory.</summary>
- public interface IControllerFactory {
- /// <summary>Creates the specified controller by using the specified request context.</summary>
- /// <returns>The controller.</returns>
- /// <param name="requestContext">The request context.</param>
- /// <param name="controllerName">The name of the controller.</param>
- IController CreateController(RequestContext requestContext, string controllerName);
- /// <summary>Gets the controller's session behavior.</summary>
- /// <returns>The controller's session behavior.</returns>
- /// <param name="requestContext">The request context.</param>
- /// <param name="controllerName">The name of the controller whose session behavior you want to get.</param>
- SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
- /// <summary>Releases the specified controller.</summary>
- /// <param name="controller">The controller.</param>
- void ReleaseController(IController controller);
- }
IControllerFactory 接口是对管理 Controller 对象的工厂的抽象,怎么管理呢,无非就是我要创建一个 Controller 就能创建一个 Controller。我们创建好了 Controller,不使用的情况就要能销毁它,能创建和能销毁就构成了 IControllerFactory 的主要功能,CreateController 用于创建 Controller 对象实例,ReleaseController 方法用于销毁不用的 Controller 对象,因为我们的 Controller 实现了 IDisposable 接口。想起来了吗?因为我们自定义的 Controller 都继承了抽象基类 Controller 类型,该抽象 Controller 基类实现了 IDisposable 接口。
IControllerFactory 接口还有一个方法 GetControllerSessionBehavior,用于管理 Controller 的会话状态,返回的类型为 SessionStateBehavior 枚举值,它有四个枚举值:Default(使用默认的 ASP.NET 逻辑来确定请求的会话行为),Required(为请求启用完全的读和写会话状态的行为),ReadOnly(为请求启用只读的会话状态),Disabled(禁用会话状态)。说起来话长,在 System.Web.SessionState 命名空间下有两个接口定义,分别是 IRequiresSessionState(实现本接口,HttpHandler 采用 Required 会话模式)和 IReadOnlySessionState(实现本接口,HttpHandler 采用 ReadOnly 会话模式)类型,这个两个接口是标记接口,没有定义具体的方法。我们在看看我们的 MvcHandler 类型的定义吧,代码如下:
- public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState
具体采用何种会话状态行为取决于当前 Http 请求上下文(HttpContext 的静态属性 Current 表示的对象),对于 ASP.NET 3.0 及其以前的版本,我们不能对当前的 Http 上下文的会话状态行为模式进行修改,在 ASP.NET 4.0 中为 HttpContext 对象定义了一个 SetSessionStateBehavior 方法,此方法也在 HttpContextBase 定义了,HttpContextBase 的子类 HttpContextWrapper 重写了这个方法,在内部调用 HttpContext 的同名方法来设置当前请求的会话状态模式。
4、ControllerBuilder
我们有了 Controller,可以通过 IController,IAsyncController,ControllerBase 或者 Controller 抽象基类来自定义我们自己的 Controller 基类型,我们也有了 IControllerFactory 接口,可以管理 Controller 对象,也可以定义我们自己的 ControllerFactory 来取代系统的默认实现。问题来了,我们自定义了 ControllerFactory 对象,但是如何把我们自定义的 ControllerFactory 放到 ASP.NET MVC 框架中呢,那就需要我们这个类型了,它的名字就是 ControllerBuilder。
代码最有说服力,我们先上代码吧,源码如下:
- /// <summary>Represents a class that is responsible for dynamically building a controller.</summary>
- public class ControllerBuilder
- {
- private static ControllerBuilder _instance = new ControllerBuilder();
- private Func<IControllerFactory> _factoryThunk = () => null;
- private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- private IResolver<IControllerFactory> _serviceResolver;
- /// <summary>Gets the current controller builder object.</summary>
- /// <returns>The current controller builder.</returns>
- public static ControllerBuilder Current
- {
- get
- {
- return ControllerBuilder._instance;
- }
- }
- /// <summary>Gets the default namespaces.</summary>
- /// <returns>The default namespaces.</returns>
- public HashSet<string> DefaultNamespaces
- {
- get
- {
- return this._namespaces;
- }
- }
- /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerBuilder" /> class.</summary>
- public ControllerBuilder() : this(null)
- {
- }
- internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver)
- {
- IResolver<IControllerFactory> arg_6A_1 = serviceResolver;
- if (serviceResolver == null)
- {
- arg_6A_1 = new SingleServiceResolver<IControllerFactory>(() => this._factoryThunk(), new DefaultControllerFactory
- {
- ControllerBuilder = this
- }, "ControllerBuilder.GetControllerFactory");
- }
- this._serviceResolver = arg_6A_1;
- }
- /// <summary>Gets the associated controller factory.</summary>
- /// <returns>The controller factory.</returns>
- public IControllerFactory GetControllerFactory()
- {
- return this._serviceResolver.Current;
- }
- /// <summary>Sets the specified controller factory.</summary>
- /// <param name="controllerFactory">The controller factory.</param>
- /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerFactory" /> parameter is null.</exception>
- public void SetControllerFactory(IControllerFactory controllerFactory)
- {
- if (controllerFactory == null)
- {
- throw new ArgumentNullException("controllerFactory");
- }
- this._factoryThunk = (() => controllerFactory);
- }
- /// <summary>Sets the controller factory by using the specified type.</summary>
- /// <param name="controllerFactoryType">The type of the controller factory.</param>
- /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerFactoryType" /> parameter is null.</exception>
- /// <exception cref="T:System.ArgumentException">The controller factory cannot be assigned from the type in the <paramref name="controllerFactoryType" /> parameter.</exception>
- /// <exception cref="T:System.InvalidOperationException">An error occurred while the controller factory was being set.</exception>
- public void SetControllerFactory(Type controllerFactoryType)
- {
- if (controllerFactoryType == null)
- {
- throw new ArgumentNullException("controllerFactoryType");
- }
- if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType))
- {
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_MissingIControllerFactory, new object[]
- {
- controllerFactoryType
- }), "controllerFactoryType");
- }
- this._factoryThunk = delegate
- {
- IControllerFactory result;
- try
- {
- result = (IControllerFactory)Activator.CreateInstance(controllerFactoryType);
- }
- catch (Exception innerException)
- {
- throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_ErrorCreatingControllerFactory, new object[]
- {
- controllerFactoryType
- }), innerException);
- }
- return result;
- };
- }
- }
源码不是很多,我贴出了完整的代码。
ControllerBuilder 定义了一个静态只读属性 Current,返回当前使用的 ControllerBuilder 对象。该类型有一个返回类型为 IControllerFactory 的方法 GetControllerFactory,这个方法用于获取当前注册的 ControllerFactory 对象。ControllerBuilder 类型还有两个重载的 SetControllerFactory 方法,这两个方法的主要作用是把我们自定义的 ControllerFactory 注册到 ASP.NET MVC 框架中去。但是这两个方法有些不同,参数类型为 IControllerFactory 的 SetControllerFactory 方法直接注册 ControllerFactory 实例对象,而参数类型为 Type 的 SetControllerFactory 方法注册的 ControllerFactory 的类型。
如果我们注册的是 ControllerFactory 类型的话,那么 GetControllerFactory 方法在获取 ControllerFactory 对象的时候都要通过反射来获得,也就是说针对 GetControllerFactory 方法的每次调用都会伴随着 ControllerFactory 的实例化,ASP.NET MVC 框架不会对实例化的 ControllerFactory 进行缓存。如果我们注册的是 ControllerFactory 对象实例,针对 GetControllerFactory 方法来说是直接返回注册的对象,就性能而言,注册 ControllerFactory 对象比较好。
命名空间
如果在多个命名空间下定义了多个同名的 Controller 类型,如果只是按着名称来匹配就会导致激活系统无法确定具体的 Controller 的类型而抛出异常。为了解决这个问题,我们必须为定义了同名的 Controller 类型的命名空间设置不同的优先级。
ASP.NET MVC 的 Controller 的激活系统为我们提供了两种提升命名空间优先级的方法。第一种方法就是通过调用 RouteCollectionExtensions 的扩展方法 MapRoute 的时候提供命名空间列表。此种方式指定的命名空间列表会保存在 Route 路由对象的 DataTokens 属性中,对应的 Key 是 Namespaces。第二种方式就是把命名空间列表添加到当前的 ControllerBuilder 类型的默认命名空间列表中。这些命名空间列表存放在返回类型为 HashSet<string> 的 DefaultNamespaces 属性中。这两种方法的优先级第一种方法更高。
对于 Area 的命名空间来说,如果我们在调用 AreaRegistrationContext 对象的 MapRoute 方法时提供了命名空间,该命名空间会作为 Route 对象的命名空间,如果没有提供,则 AreaRegistration 类型所在的命名空间,再加上 ".*" 作为 Route 对象的命名空间。当我们调用 AreaRegistrationContext 的 MapRoute 方法的时候,会在 Route 对象的 DataTokens 属性里增加一个 Key 为 UseNamespaceFallback 的变量,它表示是否采用后备的命名空间来解析 Controller 类型。如果 Route 对象有命名空间,该值就是 False,否则就是 true。
解析过程是,ASP.NET MVC 会先使用 RouteData 包含的命名空间,如果解析失败,它会从 RouteData 对象的 DataTokens 属性中取出 Key 为 UseNamespaceFallback 的值,如果该值为 true 或者不存在,就使用 ControllerBuidler 的默认命名空列表来解析。如果 UseNamespaceFallback 的值为 false,就不实用后备的命名空间,如果没找到就会抛出异常。
三、激活 Controller 类型的缓存和释放
一个比较大的系统里面,可能所涉及到的 Controller 类型成千上万,如果每次都要通过反射的方式及时获取,那对性能的影响是很客观的,ASP.NET MVC 框架组想到了这一点,针对 Controller 和 AreaRegistration 都实现了彻底的缓存,这个缓存是把所有解析出来的类型持久化到物理文件中,两个物理文件的名称分别是:MVC-ControllerTypeCache.xml 和 MVC-AreaRegistrationTypeCache.xml,具体的目录在 %windir%\Microsoft.Net\Framework\v{version}\Temporary ASP.NET Files\root\..\..\UserCache\。
当接收到 web 应用被启动后的第一个请求的时候,Controller 激活系统会读取这些文件,通过反序列化生成 List<Type> 对象。
MVC-ControllerTypeCache.xml 代码如下:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!--This file is automatically generated. Please do not modify the contents
- of this file.-->
- -
- <typeCache mvcVersionId="cc73190b-ab9d-435c-8315-10ff295c572a" lastModified="2017/6/23 18:34:17">
- -
- <assembly name="MVCTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
- -
- <module versionId="1949a001-c30a-4d56-ac65-d1714f608b76">
- <type>
- MVCTest.Controllers.AccountController
- </type>
- <type>
- MVCTest.Controllers.HomeController
- </type>
- <type>
- MVCTest.Controllers.ManageController
- </type>
- </module>
- </assembly>
- </typeCache>
Controller 的释放很简单,直接上代码吧。
- /// <summary>Releases the specified controller.</summary>
- /// <param name="controller">The controller to release.</param>
- public virtual void ReleaseController(IController controller)
- {
- IDisposable disposable = controller as IDisposable;
- if (disposable != null)
- {
- disposable.Dispose();
- }
- }
由于所有的 Controller 类型都实现了 IDisposable 接口,所以我们可以直接调用当前 Controller 对象的 Dispose 方法即可。
四、小结
好了,这篇文章就先写到这里吧,Controller 的激活系统还没有完,今天只是从总体上来讲一下,内容很多,很难在一片文章里把所有东西都写到。这个概览先给大家一个整体上的感觉吧,下一篇文章就具体写一下 Controller 的激活解析过程。
写一写很不错,把自己的理解写出来,现在更清晰了,也希望高手来指正和赐教。
来源: http://www.cnblogs.com/PatrickLiu/p/7326916.html