白話ASP.NET MVC之三:Controller是如何解析出來的
我們在上一篇文章中介紹Controller啟用系統中所涉及到的一些型別,比如有關Controller型別的相關定義型別就包括了IController型別,IAsyncController型別,ControllerBase抽象型別和我們最終要使用的抽象型別Controller,這是ASP.NET MVC 框架中和Controller本身定義相關的型別。其他輔助型別,包括管理Controller的型別ControllerFactory,這個工廠負責Controller的生產和銷燬。我們還涉及到另一個輔助型別,用於把系統預設定義的或者自定義的ControllerFactory註冊到ASP.NET MVC框架中的型別ControllerBuilder。
Controller型別、ControllerFactory型別和ControllerBuilder型別,他們之間的關係可以描述為:ControllerBuilder是面向客戶的,或者說是程式設計師和ASP.NET MVC框架之間的橋樑。我們通過ControllerBuilder型別的SetControllerFactory方法把我們自定義的ControllerFactory型別例項註冊到ASP.NET MVC框架中,ControllerFactory型別用於管理Controller型別例項,其實也就是說ControllerFactory型別就是ASP.NET MVC框架中的一個擴充套件點。
我們今天主要講Controller是怎麼解析出來的,之所以把這一部分分開寫,因為合在一起太長了,也說的不詳細,如果大家對以上說的不太清楚,可以檢視《白話ASP.NET MVC之二:Controller啟用系統的概覽》, 該文對ASP.NET MVC框架中所提到的Controlelr啟用系統所涉及的型別有詳細的介紹。
一、“路由系統”和“啟用系統”是怎麼關聯起來的
上一篇文章有過講述,我們在這裡簡單說一下。ASP.NET 的路由系統是建立在一個叫做UrlRoutingModule的HttpModule元件上的,針對請求的路由解析是通過註冊HttpApplication物件的PostResolveRequestCache事件來實現的,為當前的請求動態對映到一個HttpHandler型別上,最終由該HttpHandler接管請求並處理。我們來看看UrlRoutingModule型別的程式碼吧。
1 public class UrlRoutingModule : IHttpModule 2 { 3 private static readonly object _contextKey = new object(); 4 5 private static readonly object _requestDataKey = new object(); 6 7 private RouteCollection _routeCollection; 8 9 public RouteCollection RouteCollection 10 { 11 get 12 { 13 if (this._routeCollection == null) 14 { 15 this._routeCollection = RouteTable.Routes; 16 } 17 return this._routeCollection; 18 } 19 set 20 { 21 this._routeCollection = value; 22 } 23 } 24 25 protected virtual void Dispose() 26 { 27 } 28 29 protected virtual void Init(HttpApplication application) 30 { 31 if (application.Context.Items[UrlRoutingModule._contextKey] != null) 32 { 33 return; 34 } 35 application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey; 36 application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache); 37 } 38 39 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) 40 { 41 HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context); 42 this.PostResolveRequestCache(context); 43 } 44 45 [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")] 46 public virtual void PostMapRequestHandler(HttpContextBase context) 47 { 48 } 49 50 public virtual void PostResolveRequestCache(HttpContextBase context) 51 { 52 RouteData routeData = this.RouteCollection.GetRouteData(context); 53 if (routeData == null) 54 { 55 return; 56 } 57 IRouteHandler routeHandler = routeData.RouteHandler; 58 if (routeHandler == null) 59 { 60 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0])); 61 } 62 if (routeHandler is StopRoutingHandler) 63 { 64 return; 65 } 66 RequestContext requestContext = new RequestContext(context, routeData); 67 context.Request.RequestContext = requestContext; 68 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); 69 if (httpHandler == null) 70 { 71 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] 72 { 73 routeHandler.GetType() 74 })); 75 } 76 if (!(httpHandler is UrlAuthFailureHandler)) 77 { 78 context.RemapHandler(httpHandler); 79 return; 80 } 81 if (FormsAuthenticationModule.FormsAuthRequired) 82 { 83 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); 84 return; 85 } 86 throw new HttpException(401, SR.GetString("Assess_Denied_Description3")); 87 } 88 89 void IHttpModule.Dispose() 90 { 91 this.Dispose(); 92 } 93 94 void IHttpModule.Init(HttpApplication application) 95 { 96 this.Init(application); 97 } 98 }
具體來說,該元件通過以RouteTable的靜態屬性Routes表示的路由錶針對當前請求實施路由解析,如果有匹配,就會根據路由物件Route來生成RouteData路由資料物件,然後我們藉助RouteData物件的RouteHandler屬性獲取想要的HttpHandler物件。在預設情況下這個RouteHandler屬性所代表的物件是MvcRouteHandler。翠花,上程式碼:
1 /// <summary>Creates an object that implements the IHttpHandler interface and passes the request context to it.</summary> 2 public class MvcRouteHandler : IRouteHandler 3 { 4 private IControllerFactory _controllerFactory; 5 6 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.MvcRouteHandler" /> class.</summary> 7 public MvcRouteHandler() 8 { 9 } 10 11 /// <summary>Initializes a new instance of the <see cref="T:System.Web.Mvc.MvcRouteHandler" /> class using the specified factory controller object.</summary> 12 /// <param name="controllerFactory">The controller factory.</param> 13 public MvcRouteHandler(IControllerFactory controllerFactory) 14 { 15 this._controllerFactory = controllerFactory; 16 } 17 18 /// <summary>Returns the HTTP handler by using the specified HTTP context.</summary> 19 /// <returns>The HTTP handler.</returns> 20 /// <param name="requestContext">The request context.</param> 21 protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) 22 { 23 requestContext.HttpContext.SetSessionStateBehavior(this.GetSessionStateBehavior(requestContext)); 24 return new MvcHandler(requestContext); 25 } 26 27 /// <summary>Returns the session behavior.</summary> 28 /// <returns>The session behavior.</returns> 29 /// <param name="requestContext">The request context.</param> 30 protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) 31 { 32 string text = (string)requestContext.RouteData.Values["controller"]; 33 if (string.IsNullOrWhiteSpace(text)) 34 { 35 throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController); 36 } 37 IControllerFactory controllerFactory = this._controllerFactory ?? ControllerBuilder.Current.GetControllerFactory(); 38 return controllerFactory.GetControllerSessionBehavior(requestContext, text); 39 } 40 41 /// <summary>Returns the HTTP handler by using the specified request context.</summary> 42 /// <returns>The HTTP handler.</returns> 43 /// <param name="requestContext">The request context.</param> 44 IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) 45 { 46 return this.GetHttpHandler(requestContext); 47 } 48 }
在該型別裡面包含了一個IControllerFactory型別成員欄位,這個介面型別是所有ControllerFactory都要必須實現的介面,否則就不叫Controller的工廠了。MvcRouteHandler型別有兩個建構函式,無參的沒的說,另一個需要傳遞一個IControllerFactory型別的引數,這個引數用於初始化MvcRouteHandler型別內部包含的型別為IControllerFactory的_controllerFactory欄位。當我們構造MvcRouteHandler例項的時候,如果我們呼叫了無參的建構函式,它會在內部使用ControllerBuilder.Current.GetControllerFactory()方法來獲取我們通過ControllerBuilder型別註冊的IControllerFactory型別的例項,程式碼很明顯:
IControllerFactory controllerFactory = this._controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
MvcRouteHandler實現了IRouteHandler介面,目的只有一個,提供後續的HttpHandler,IRouteHandler介面定義如下:
1 public interface IRouteHandler 2 { 3 IHttpHandler GetHttpHandler(RequestContext requestContext); 4 }
MvcRouteHandler會給我們直接返回MvcHandler物件,這個物件用於處理請求,包括啟用Controler物件,程式碼最有說服力,這份程式碼,上篇文章也貼過,現在也貼一下把,上程式碼:
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 { ValidationUtility.EnableDynamicValidation(current); 215 } this.AddVersionHeader(httpContext); 216 this.RemoveOptionalRoutingParameters(); 217 string requiredString = this.RequestContext.RouteData.GetRequiredString("controller"); 218 factory = this.ControllerBuilder.GetControllerFactory(); 219 controller = factory.CreateController(this.RequestContext, requiredString); 220 if (controller == null) 221 { 222 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] 223 { 224 factory.GetType(), 225 requiredString 226 })); 227 } 228 } 229 230 private void RemoveOptionalRoutingParameters() 231 { 232 RouteValueDictionary values = this.RequestContext.RouteData.Values; 233 values.RemoveFromDictionary((KeyValuePair<string, object> entry) => entry.Value == UrlParameter.Optional); 234 } 235 236 /// <summary>Enables processing of HTTP Web requests by a custom HTTP handler that implements the <see cref="T:System.Web.IHttpHandler" /> interface.</summary> 237 /// <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> 238 void IHttpHandler.ProcessRequest(HttpContext httpContext) 239 { 240 this.ProcessRequest(httpContext); 241 } 242 243 /// <summary>Called by ASP.NET to begin asynchronous request processing using the base HTTP context.</summary> 244 /// <returns>The status of the asynchronous call.</returns> 245 /// <param name="context">The HTTP context.</param> 246 /// <param name="cb">The asynchronous callback method.</param> 247 /// <param name="extraData">The data.</param> 248 IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) 249 { 250 return this.BeginProcessRequest(context, cb, extraData); 251 } 252 253 /// <summary>Called by ASP.NET when asynchronous request processing