首页 > 代码库 > 白话ASP.NET MVC之二:Controller激活系统的概览
白话ASP.NET MVC之二:Controller激活系统的概览
前文简介:我们抽象类路由规则的对象,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的源码吧,有助于我们的理解,代码如下:
1 /// <summary>Selects the controller that will handle an HTTP request.</summary> 2 public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState 3 { 4 private struct ProcessRequestState 5 { 6 internal IAsyncController AsyncController; 7 8 internal IControllerFactory Factory; 9 10 internal RequestContext RequestContext; 11 12 internal void ReleaseController() 13 { 14 this.Factory.ReleaseController(this.AsyncController); 15 } 16 } 17 18 private static readonly object _processRequestTag = new object(); 19 20 internal static readonly string MvcVersion = MvcHandler.GetMvcVersionString(); 21 22 /// <summary>Contains the header name of the ASP.NET MVC version.</summary> 23 public static readonly string MvcVersionHeaderName = "X-AspNetMvc-Version"; 24 25 private ControllerBuilder _controllerBuilder; 26 27 internal ControllerBuilder ControllerBuilder 28 { 29 get 30 { 31 if (this._controllerBuilder == null) 32 { 33 this._controllerBuilder = ControllerBuilder.Current; 34 } 35 return this._controllerBuilder; 36 } 37 set 38 { 39 this._controllerBuilder = value; 40 } 41 } 42 43 /// <summary>Gets or sets a value that indicates whether the MVC response header is disabled.</summary> 44 /// <returns>true if the MVC response header is disabled; otherwise, false.</returns> 45 public static bool DisableMvcResponseHeader 46 { 47 get; 48 set; 49 } 50 51 /// <summary>Gets a value that indicates whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.</summary> 52 /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns> 53 protected virtual bool IsReusable 54 { 55 get 56 { 57 return false; 58 } 59 } 60 61 /// <summary>Gets the request context.</summary> 62 /// <returns>The request context.</returns> 63 public RequestContext RequestContext 64 { 65 get; 66 private set; 67 } 68 69 /// <summary>Gets a value that indicates whether another request can use the <see cref="T:System.Web.IHttpHandler" /> instance.</summary> 70 /// <returns>true if the <see cref="T:System.Web.IHttpHandler" /> instance is reusable; otherwise, false.</returns> 71 bool IHttpHandler.IsReusable 72 { 73 get 74 { 75 return this.IsReusable; 76 } 77 } 78 79 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.MvcHandler" /> class.</summary> 80 /// <param name="requestContext">The request context.</param> 81 /// <exception cref="T:System.ArgumentNullException">The <paramref name="requestContext" /> parameter is null.</exception> 82 public MvcHandler(RequestContext requestContext) 83 { 84 if (requestContext == null) 85 { 86 throw new ArgumentNullException("requestContext"); 87 } 88 this.RequestContext = requestContext; 89 } 90 91 /// <summary>Adds the version header by using the specified HTTP context.</summary> 92 /// <param name="httpContext">The HTTP context.</param> 93 protected internal virtual void AddVersionHeader(HttpContextBase httpContext) 94 { 95 if (!MvcHandler.DisableMvcResponseHeader) 96 { 97 httpContext.Response.AppendHeader(MvcHandler.MvcVersionHeaderName, MvcHandler.MvcVersion); 98 } 99 } 100 101 /// <summary>Called by ASP.NET to begin asynchronous request processing.</summary> 102 /// <returns>The status of the asynchronous call.</returns> 103 /// <param name="httpContext">The HTTP context.</param> 104 /// <param name="callback">The asynchronous callback method.</param> 105 /// <param name="state">The state of the asynchronous object.</param> 106 protected virtual IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state) 107 { 108 HttpContextBase httpContext2 = new HttpContextWrapper(httpContext); 109 return this.BeginProcessRequest(httpContext2, callback, state); 110 } 111 112 /// <summary>Called by ASP.NET to begin asynchronous request processing using the base HTTP context.</summary> 113 /// <returns>The status of the asynchronous call.</returns> 114 /// <param name="httpContext">The HTTP context.</param> 115 /// <param name="callback">The asynchronous callback method.</param> 116 /// <param name="state">The state of the asynchronous object.</param> 117 protected internal virtual IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state) 118 { 119 IController controller; 120 IControllerFactory factory; 121 this.ProcessRequestInit(httpContext, out controller, out factory); 122 IAsyncController asyncController = controller as IAsyncController; 123 if (asyncController != null) 124 { 125 BeginInvokeDelegate<MvcHandler.ProcessRequestState> beginDelegate = delegate(AsyncCallback asyncCallback, object asyncState, MvcHandler.ProcessRequestState innerState) 126 { 127 IAsyncResult result; 128 try 129 { 130 result = innerState.AsyncController.BeginExecute(innerState.RequestContext, asyncCallback, asyncState); 131 } 132 catch 133 { 134 innerState.ReleaseController(); 135 throw; 136 } 137 return result; 138 }; 139 EndInvokeVoidDelegate<MvcHandler.ProcessRequestState> endDelegate = delegate(IAsyncResult asyncResult, MvcHandler.ProcessRequestState innerState) 140 { 141 try 142 { 143 innerState.AsyncController.EndExecute(asyncResult); 144 } 145 finally 146 { 147 innerState.ReleaseController(); 148 } 149 }; 150 MvcHandler.ProcessRequestState invokeState = new MvcHandler.ProcessRequestState 151 { 152 AsyncController = asyncController, 153 Factory = factory, 154 RequestContext = this.RequestContext 155 }; 156 SynchronizationContext synchronizationContext = SynchronizationContextUtil.GetSynchronizationContext(); 157 return AsyncResultWrapper.Begin<MvcHandler.ProcessRequestState>(callback, state, beginDelegate, endDelegate, invokeState, MvcHandler._processRequestTag, -1, synchronizationContext); 158 } 159 Action action = delegate 160 { 161 try 162 { 163 controller.Execute(this.RequestContext); 164 } 165 finally 166 { 167 factory.ReleaseController(controller); 168 } 169 }; 170 return AsyncResultWrapper.BeginSynchronous(callback, state, action, MvcHandler._processRequestTag); 171 } 172 173 /// <summary>Called by ASP.NET when asynchronous request processing has ended.</summary> 174 /// <param name="asyncResult">The asynchronous result.</param> 175 protected internal virtual void EndProcessRequest(IAsyncResult asyncResult) 176 { 177 AsyncResultWrapper.End(asyncResult, MvcHandler._processRequestTag); 178 } 179 180 private static string GetMvcVersionString() 181 { 182 return new AssemblyName(typeof(MvcHandler).Assembly.FullName).Version.ToString(2); 183 } 184 185 /// <summary>Processes the request by using the specified HTTP request context.</summary> 186 /// <param name="httpContext">The HTTP context.</param> 187 protected virtual void ProcessRequest(HttpContext httpContext) 188 { 189 HttpContextBase httpContext2 = new HttpContextWrapper(httpContext); 190 this.ProcessRequest(httpContext2); 191 } 192 193 /// <summary>Processes the request by using the specified base HTTP request context.</summary> 194 /// <param name="httpContext">The HTTP context.</param> 195 protected internal virtual void ProcessRequest(HttpContextBase httpContext) 196 { 197 IController controller; 198 IControllerFactory controllerFactory; 199 this.ProcessRequestInit(httpContext, out controller, out controllerFactory); 200 try 201 { 202 controller.Execute(this.RequestContext); 203 } 204 finally 205 { 206 controllerFactory.ReleaseController(controller); 207 } 208 } 209 210 private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) 211 { 212 HttpContext current = HttpContext.Current; 213 if (current != null && ValidationUtility.IsValidationEnabled(current) == true) 214 { 215 ValidationUtility.EnableDynamicValidation(current); 216 } 217 this.AddVersionHeader(httpContext); 218 this.RemoveOptionalRoutingParameters(); 219 string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); 220 factory = this.ControllerBuilder.GetControllerFactory(); 221 controller = factory.CreateController(this.RequestContext, requiredString); 222 if (controller == null) 223 { 224 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] 225 { 226 factory.GetType(), 227 requiredString 228 })); 229 } 230 } 231 232 private void RemoveOptionalRoutingParameters() 233 { 234 RouteValueDictionary values = this.RequestContext.RouteData.Values; 235 values.RemoveFromDictionary((KeyValuePair<string, object> entry) => entry.Value =http://www.mamicode.com/= UrlParameter.Optional); 236 } 237 238 /// <summary>Enables processing of HTTP Web requests by a custom HTTP handler that implements the <see cref="T:System.Web.IHttpHandler" /> interface.</summary> 239 /// <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> 240 void IHttpHandler.ProcessRequest(HttpContext httpContext) 241 { 242 this.ProcessRequest(httpContext); 243 } 244 245 /// <summary>Called by ASP.NET to begin asynchronous request processing using the base HTTP context.</summary> 246 /// <returns>The status of the asynchronous call.</returns> 247 /// <param name="context">The HTTP context.</param> 248 /// <param name="cb">The asynchronous callback method.</param> 249 /// <param name="extraData">The data.</param> 250 IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) 251 { 252 return this.BeginProcessRequest(context, cb, extraData); 253 } 254 255 /// <summary>Called by ASP.NET when asynchronous request processing has ended.</summary> 256 /// <param name="result">The asynchronous result.</param> 257 void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) 258 { 259 this.EndProcessRequest(result); 260 } 261 }
MvcHandler里面这个两个方法定义了Controller激活系统实现的骨架:
1 /// <summary>Processes the request by using the specified base HTTP request context.</summary> 2 /// <param name="httpContext">The HTTP context.</param> 3 protected internal virtual void ProcessRequest(HttpContextBase httpContext) 4 { 5 IController controller; 6 IControllerFactory controllerFactory; 7 this.ProcessRequestInit(httpContext, out controller, out controllerFactory); 8 try 9 { 10 controller.Execute(this.RequestContext); 11 } 12 finally 13 { 14 controllerFactory.ReleaseController(controller); 15 } 16 } 17 18 private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) 19 { 20 HttpContext current = HttpContext.Current; 21 if (current != null && ValidationUtility.IsValidationEnabled(current) == true) 22 { 23 ValidationUtility.EnableDynamicValidation(current); 24 } 25 this.AddVersionHeader(httpContext); 26 this.RemoveOptionalRoutingParameters(); 27 string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); 28 factory = this.ControllerBuilder.GetControllerFactory(); 29 controller = factory.CreateController(this.RequestContext, requiredString); 30 if (controller == null) 31 { 32 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] 33 { 34 factory.GetType(), 35 requiredString 36 })); 37 } 38 }
代码不是很难,大家一看就能看懂,下一节好好的介绍一些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方法来实现。我们再看看异步版的接口定义吧:
1 public interface IAsyncController : IController 2 { 3 /// <summary>Executes the specified request context.</summary> 4 /// <returns>The status of the asynchronous operation.</returns> 5 /// <param name="requestContext">The request context.</param> 6 /// <param name="callback">The asynchronous callback method.</param> 7 /// <param name="state">The state.</param> 8 IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state); 9 10 /// <summary>Ends the asynchronous operation.</summary> 11 /// <param name="asyncResult">The asynchronous result.</param> 12 void EndExecute(IAsyncResult asyncResult); 13 }
异步版接口定义的命名空间是: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,
1 /// <summary>Represents the base class for all MVC controllers.</summary> 2 public abstract class ControllerBase : IController 3 { 4 private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate(); 5 6 private DynamicViewDataDictionary _dynamicViewDataDictionary; 7 8 private TempDataDictionary _tempDataDictionary; 9 10 private bool _validateRequest = true; 11 12 private IValueProvider _valueProvider; 13 14 private ViewDataDictionary _viewDataDictionary; 15 16 /// <summary>Gets or sets the controller context.</summary> 17 /// <returns>The controller context.</returns> 18 public ControllerContext ControllerContext 19 { 20 get; 21 set; 22 } 23 24 /// <summary>Gets or sets the dictionary for temporary data.</summary> 25 /// <returns>The dictionary for temporary data.</returns> 26 public TempDataDictionary TempData 27 { 28 get 29 { 30 if (this.ControllerContext != null && this.ControllerContext.IsChildAction) 31 { 32 return this.ControllerContext.ParentActionViewContext.TempData; 33 } 34 if (this._tempDataDictionary == null) 35 { 36 this._tempDataDictionary = new TempDataDictionary(); 37 } 38 return this._tempDataDictionary; 39 } 40 set 41 { 42 this._tempDataDictionary = value; 43 } 44 } 45 46 /// <summary>Gets or sets a value that indicates whether request validation is enabled for this request.</summary> 47 /// <returns>true if request validation is enabled for this request; otherwise, false. The default is true.</returns> 48 public bool ValidateRequest 49 { 50 get 51 { 52 return this._validateRequest; 53 } 54 set 55 { 56 this._validateRequest = value; 57 } 58 } 59 60 /// <summary>Gets or sets the value provider for the controller.</summary> 61 /// <returns>The value provider for the controller.</returns> 62 public IValueProvider ValueProvider 63 { 64 get 65 { 66 if (this._valueProvider == null) 67 { 68 this._valueProvider = ValueProviderFactories.Factories.GetValueProvider(this.ControllerContext); 69 } 70 return this._valueProvider; 71 } 72 set 73 { 74 this._valueProvider = value; 75 } 76 } 77 78 /// <summary>Gets the dynamic view data dictionary.</summary> 79 /// <returns>The dynamic view data dictionary.</returns> 80 [Dynamic] 81 public dynamic ViewBag 82 { 83 [return: Dynamic] 84 get 85 { 86 if (this._dynamicViewDataDictionary == null) 87 { 88 this._dynamicViewDataDictionary = new DynamicViewDataDictionary(() => this.ViewData); 89 } 90 return this._dynamicViewDataDictionary; 91 } 92 } 93 94 /// <summary>Gets or sets the dictionary for view data.</summary> 95 /// <returns>The dictionary for the view data.</returns> 96 public ViewDataDictionary ViewData 97 { 98 get 99 { 100 if (this._viewDataDictionary == null) 101 { 102 this._viewDataDictionary = new ViewDtaDictionary(); 103 } 104 return this._viewDataDictionary; 105 } 106 set 107 { 108 this._viewDataDictionary = value; 109 } 110 } 111 /// <summary>Executes the specified request context.</summary> 112 /// <param name="requestContext">The request context.</param> 113 /// <exception cref="T:System.ArgumentNullException">The <paramref name="requestContext" /> parameter is null.</exception> 114 protected virtual void Execute(RequestContext requestContext) 115 { 116 if (requestContext == null) 117 { 118 throw new ArgumentNullException("requestContext"); 119 } 120 if (requestContext.HttpContext == null) 121 { 122 throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext"); 123 } 124 this.VerifyExecuteCalledOnce(); 125 this.Initialize(requestContext); 126 using (ScopeStorage.CreateTransientScope()) 127 { 128 this.ExecuteCore(); 129 } 130 } 131 132 /// <summary>Executes the request.</summary> 133 protected abstract void ExecuteCore(); 134 135 /// <summary>Initializes the specified request context.</summary> 136 /// <param name="requestContext">The request context.</param> 137 protected virtual void Initialize(RequestContext requestContext) 138 { 139 this.ControllerContext = new ControllerContext(requestContext, this); 140 } 141 142 internal void VerifyExecuteCalledOnce() 143 { 144 if (!this._executeWasCalledGate.TryEnter()) 145 { 146 string message = string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, new object[] 147 { 148 base.GetType() 149 }); 150 throw new InvalidOperationException(message); 151 } 152 } 153 154 /// <summary>Executes the specified request context.</summary> 155 /// <param name="requestContext">The request context.</param> 156 void IController.Execute(RequestContext requestContext) 157 { 158 this.Execute(requestContext); 159 } 160 }
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。
1 protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state) 2 { 3 if (this.DisableAsyncSupport) 4 { 5 Action action = delegate 6 { 7 this.Execute(requestContext); 8 }; 9 return AsyncResultWrapper.BeginSynchronous(callback, state, action, Controller._executeTag); 10 } 11 if (requestContext == null) 12 { 13 throw new ArgumentNullException("requestContext"); 14 } 15 16 this.Initialize(requestContext); 17 BeginInvokeDelegate<Controller> beginDelegate = (AsyncCallback asyncCallback, object callbackState, Controller controller) => controller.BeginExecuteCore(asyncCallback, callbackState); 18 EndInvokeVoidDelegate<Controller> endDelegate = delegate(IAsyncResult asyncResult, Controller controller) 19 { 20 controller.EndExecuteCore(asyncResult); 21 }; 22 return AsyncResultWrapper.Begin<Controller>(callback, state, beginDelegate, endDelegate, this, Controller._executeTag, -1, null); 23 }
2、ControllerContext
在ASP.NET MVC中我们会遇到很多上下文(Context)的类型,比如:RequestContext,它是对HttpContext和RouteData的封装,今天我们看看ControllerContext,就是指Controller的上下文,再说明白一点就是Controller在执行过程中对需要用到的一些数据的封装对象,它类似一个Facade,里面包含了一些在Controller执行时所要用的一些对象,必须表示请求的HttpContextBase对象。这个类型没什么可说的,很简单,我把反编译的代码也都贴出来了,大家可以自己看。
1 /// <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> 2 public class ControllerContext 3 { 4 private sealed class EmptyHttpContext : HttpContextBase 5 { 6 } 7 8 internal const string ParentActionViewContextToken = "ParentActionViewContext"; 9 10 private HttpContextBase _httpContext; 11 12 private RequestContext _requestContext; 13 14 private RouteData _routeData; 15 16 /// <summary>Gets or sets the controller.</summary> 17 /// <returns>The controller.</returns> 18 public virtual ControllerBase Controller 19 { 20 get; 21 set; 22 } 23 24 /// <summary>Gets the display mode.</summary> 25 /// <returns>The display mode.</returns> 26 public IDisplayMode DisplayMode 27 { 28 get 29 { 30 return DisplayModeProvider.GetDisplayMode(this.HttpContext); 31 } 32 set 33 { 34 DisplayModeProvider.SetDisplayMode(this.HttpContext, value); 35 } 36 } 37 38 /// <summary>Gets or sets the HTTP context.</summary> 39 /// <returns>The HTTP context.</returns> 40 public virtual HttpContextBase HttpContext 41 { 42 get 43 { 44 if (this._httpContext == null) 45 { 46 this._httpContext = ((this._requestContext != null) ? this._requestContext.HttpContext : new ControllerContext.EmptyHttpContext()); 47 } 48 return this._httpContext; 49 } 50 set 51 { 52 this._httpContext = value; 53 } 54 } 55 56 /// <summary>Gets a value that indicates whether the associated action method is a child action.</summary> 57 /// <returns>true if the associated action method is a child action; otherwise, false.</returns> 58 public virtual bool IsChildAction 59 { 60 get 61 { 62 RouteData routeData = http://www.mamicode.com/this.RouteData; 63 return routeData != null && routeData.DataTokens.ContainsKey("ParentActionViewContext"); 64 } 65 } 66 67 /// <summary>Gets an object that contains the view context information for the parent action method.</summary> 68 /// <returns>An object that contains the view context information for the parent action method.</returns> 69 public ViewContext ParentActionViewContext 70 { 71 get 72 { 73 return this.RouteData.DataTokens["ParentActionViewContext"] as ViewContext; 74 } 75 } 76 77 /// <summary>Gets or sets the request context.</summary> 78 /// <returns>The request context.</returns> 79 public RequestContext RequestContext 80 { 81 get 82 { 83 if (this._requestContext == null) 84 { 85 HttpContextBase httpContext = this.HttpContext ?? new ControllerContext.EmptyHttpContext(); 86 RouteData routeData = http://www.mamicode.com/this.RouteData ?? new RouteData(); 87 this._requestContext = new RequestContext(httpContext, routeData); 88 } 89 return this._requestContext; 90 } 91 set 92 { 93 this._requestContext = value; 94 } 95 } 96 97 /// <summary>Gets or sets the URL route data.</summary> 98 /// <returns>The URL route data.</returns> 99 public virtual RouteData RouteData 100 { 101 get 102 { 103 if (this._routeData =http://www.mamicode.com/= null) 104 { 105 this._routeData = http://www.mamicode.com/((this._requestContext != null) ? this._requestContext.RouteData : new RouteData()); 106 } 107 return this._routeData; 108 } 109 set 110 { 111 this._routeData =http://www.mamicode.com/ value; 112 } 113 } 114 115 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class.</summary> 116 public ControllerContext() 117 { 118 } 119 120 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class by using the specified controller context.</summary> 121 /// <param name="controllerContext">The controller context.</param> 122 /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext" /> parameter is null.</exception> 123 protected ControllerContext(ControllerContext controllerContext) 124 { 125 if (controllerContext == null) 126 { 127 throw new ArgumentNullException("controllerContext"); 128 } 129 this.Controller = controllerContext.Controller; 130 this.RequestContext = controllerContext.RequestContext; 131 } 132 133 /// <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> 134 /// <param name="httpContext">The HTTP context.</param> 135 /// <param name="routeData">The route data.</param> 136 /// <param name="controller">The controller.</param> 137 public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller) : this(new RequestContext(httpContext, routeData), controller) 138 { 139 } 140 141 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerContext" /> class by using the specified request context and controller.</summary> 142 /// <param name="requestContext">The request context.</param> 143 /// <param name="controller">The controller.</param> 144 /// <exception cref="T:System.ArgumentNullException">One or both parameters are null.</exception> 145 public ControllerContext(RequestContext requestContext, ControllerBase controller) 146 { 147 if (requestContext == null) 148 { 149 throw new ArgumentNullException("requestContext"); 150 } 151 if (controller == null) 152 { 153 throw new ArgumentNullException("controller"); 154 } 155 this.RequestContext = requestContext; 156 this.Controller = controller; 157 } 158 }
3、ControllerFactory
我们的Controller对象有了,为了解耦和可扩展性,ASP.NET MVC 框架中不可能直接new一个Controller的实例对象吧,我们在MvcHandler的代码中也看到了是基于接口编程的,所以需要提供一种机制,来提供我们要得到的Controller对象。既然不能直接new一个Controller出来,那我们就封装new的过程,用工厂来代替吧。对了,就是工厂,这个工厂就是ControllerFactory,从名字上可以看出它的意思,创建Controller对象的工厂。ASP.NET MVC是可以扩展的,可以自定义的,我们的ControllerFactory工厂也不例外,所以我们就产生了ControllerFactory工厂的接口,接口的名字是IControllerFactory,所有的ControllerFactory都必须实现该接口。接口定义如下:
1 /// <summary>Defines the methods that are required for a controller factory.</summary> 2 public interface IControllerFactory 3 { 4 /// <summary>Creates the specified controller by using the specified request context.</summary> 5 /// <returns>The controller.</returns> 6 /// <param name="requestContext">The request context.</param> 7 /// <param name="controllerName">The name of the controller.</param> 8 IController CreateController(RequestContext requestContext, string controllerName); 9 10 /// <summary>Gets the controller‘s session behavior.</summary> 11 /// <returns>The controller‘s session behavior.</returns> 12 /// <param name="requestContext">The request context.</param> 13 /// <param name="controllerName">The name of the controller whose session behavior you want to get.</param> 14 SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName); 15 16 /// <summary>Releases the specified controller.</summary> 17 /// <param name="controller">The controller.</param> 18 void ReleaseController(IController controller); 19 }
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。
代码最有说服力,我们先上代码吧,源码如下:
1 /// <summary>Represents a class that is responsible for dynamically building a controller.</summary> 2 public class ControllerBuilder 3 { 4 private static ControllerBuilder _instance = new ControllerBuilder(); 5 6 private Func<IControllerFactory> _factoryThunk = () => null; 7 8 private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase); 9 10 private IResolver<IControllerFactory> _serviceResolver; 11 12 /// <summary>Gets the current controller builder object.</summary> 13 /// <returns>The current controller builder.</returns> 14 public static ControllerBuilder Current 15 { 16 get 17 { 18 return ControllerBuilder._instance; 19 } 20 } 21 22 /// <summary>Gets the default namespaces.</summary> 23 /// <returns>The default namespaces.</returns> 24 public HashSet<string> DefaultNamespaces 25 { 26 get 27 { 28 return this._namespaces; 29 } 30 } 31 32 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.ControllerBuilder" /> class.</summary> 33 public ControllerBuilder() : this(null) 34 { 35 } 36 37 internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) 38 { 39 IResolver<IControllerFactory> arg_6A_1 = serviceResolver; 40 if (serviceResolver == null) 41 { 42 arg_6A_1 = new SingleServiceResolver<IControllerFactory>(() => this._factoryThunk(), new DefaultControllerFactory 43 { 44 ControllerBuilder = this 45 }, "ControllerBuilder.GetControllerFactory"); 46 } 47 this._serviceResolver = arg_6A_1; 48 } 49 50 /// <summary>Gets the associated controller factory.</summary> 51 /// <returns>The controller factory.</returns> 52 public IControllerFactory GetControllerFactory() 53 { 54 return this._serviceResolver.Current; 55 } 56 57 /// <summary>Sets the specified controller factory.</summary> 58 /// <param name="controllerFactory">The controller factory.</param> 59 /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerFactory" /> parameter is null.</exception> 60 public void SetControllerFactory(IControllerFactory controllerFactory) 61 { 62 if (controllerFactory == null) 63 { 64 throw new ArgumentNullException("controllerFactory"); 65 } 66 this._factoryThunk = (() => controllerFactory); 67 } 68 69 /// <summary>Sets the controller factory by using the specified type.</summary> 70 /// <param name="controllerFactoryType">The type of the controller factory.</param> 71 /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerFactoryType" /> parameter is null.</exception> 72 /// <exception cref="T:System.ArgumentException">The controller factory cannot be assigned from the type in the <paramref name="controllerFactoryType" /> parameter.</exception> 73 /// <exception cref="T:System.InvalidOperationException">An error occurred while the controller factory was being set.</exception> 74 public void SetControllerFactory(Type controllerFactoryType) 75 { 76 if (controllerFactoryType == null) 77 { 78 throw new ArgumentNullException("controllerFactoryType"); 79 } 80 if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType)) 81 { 82 throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_MissingIControllerFactory, new object[] 83 { 84 controllerFactoryType 85 }), "controllerFactoryType"); 86 } 87 this._factoryThunk = delegate 88 { 89 IControllerFactory result; 90 try 91 { 92 result = (IControllerFactory)Activator.CreateInstance(controllerFactoryType); 93 } 94 catch (Exception innerException) 95 { 96 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_ErrorCreatingControllerFactory, new object[] 97 { 98 controllerFactoryType 99 }), innerException); 100 } 101 return result; 102 }; 103 } 104 }
源码不是很多,我贴出了完整的代码。
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的激活解析过程。
写一写很不错,把自己的理解写出来,现在更清晰了,也希望高手来指正和赐教。
白话ASP.NET MVC之二:Controller激活系统的概览