1. 程式人生 > >白話ASP.NET MVC之三:Controller是如何解析出來的

白話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