ASP.NET Web API 控制器執行過程(一)
前言
前面兩篇講解了控制器的建立過程,隻是從架構源碼的角度去簡單的了解,在控制器建立過後所執行的過程也是尤為重要的,本篇就來簡單的說明一下控制器在建立過後将會做哪些工作。
ASP.NET Web API 控制器執行過程
l ASP.NET Web API 控制器執行過程(一)
l ASP.NET Web API 控制器執行過程(二)
我們知道控制器的生成過程都是在HttpControllerDispatcher類型中來操作的,那我們要想知道控制器在建立過後執行操作的入口點也必須在HttpControllerDispatcher類型中才能發現。來看如下示例代碼:
代碼1-1
1
2
3
4
5
6
7
8
9
10
11
12
<code> </code><code>privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken)</code>
<code> </code><code>{</code>
<code> </code><code>IHttpRouteDatarouteData=request.GetRouteData();</code>
<code> </code><code>HttpControllerDescriptordescriptor=</code><code>this</code><code>.ControllerSelector.SelectController(request); </code>
<code> </code><code>IHttpControllercontroller=descriptor.CreateController(request); </code>
<code> </code><code>HttpConfigurationconfiguration=request.GetConfiguration();</code>
<code> </code><code>HttpControllerContextcontrollerContext=newHttpControllerContext(descriptor.Configuration, routeData, request) {</code>
<code> </code><code>Controller=controller,</code>
<code> </code><code>ControllerDescriptor=descriptor</code>
<code> </code><code>};</code>
<code> </code><code>returncontroller.ExecuteAsync(controllerContext, cancellationToken);</code>
<code>}</code>
看過前面兩篇的朋友看到這裡的代碼一定會很熟悉了,控制器的生成過程就包含在了其中,在代碼1-1中我們會看到HttpControllerContext類型,從它的名稱來看想必大家也都知道了它的作用,代表着進入控制器處理階段的邏輯上的上下文對象,并且封裝着一些很重要的資訊。
HttpControllerContext控制器上下文
示例代碼1-2
<code>publicclassHttpControllerContext</code>
<code> </code><code>publicHttpControllerContext();</code>
<code> </code><code>publicHttpControllerContext(HttpConfigurationconfiguration, IHttpRouteDatarouteData, HttpRequestMessagerequest);</code>
<code> </code>
<code> </code><code>publicHttpConfigurationConfiguration { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicIHttpControllerController { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicHttpControllerDescriptorControllerDescriptor { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicHttpRequestMessageRequest { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>publicIHttpRouteDataRouteData { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
結合代碼1-2和代碼1-1可以看到在HttpControllerContext類型中對HttpConfiguration類型的對象,它的重要性不用多說了,裡面包含着很多配置資訊以及存放基礎設施的容器對象,然後就是路由資料對象IHttpRouteData類型,以及最後的Http請求對象HttpRequestMessage類型的對象,并且在代碼1-1中對HttpControllerContext類型中的Controller和ControllerDescriptor屬性進行了指派,Controller屬性對應的就是目前被建立好的控制器,而ControllerDescriptor屬性則是表示Controller屬性對應控制器的描述類型,現在回頭再看一下HttpControllerContext類型的對象就知道它裡面包含的内容是有多重要了。
現在我們再回到代碼1-1中,最後我們看到是由IHttpController類型的變量controller調用方法ExecuteAsync()方法由此進入控制器中,一般控制器都是繼承自ApiController,我們就從ApiController類型來入手。
ApiController類型
示例代碼1-3
<code> </code><code>publicabstractclassApiController : IHttpController, IDisposable</code>
<code> </code><code>publicvirtualTask<System.Net.Http.HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken);</code>
在代碼1-3中我們可以看到再ApiController類型中定義了ExecuteAsync()方法,ApiController為抽象類型,控制器的主要執行過程也就是都在ExecuteAsync()方法中,下面我看一下具體的實作,如下示例代碼。
代碼1-4
13
14
15
<code>publicvirtualTask<HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken)</code>
<code> </code><code>HttpControllerDescriptorcontrollerDescriptor=controllerContext.ControllerDescriptor;</code>
<code> </code><code>ServicesContainercontrollerServices=controllerDescriptor.Configuration.Services;</code>
<code> </code><code>HttpActionDescriptoractionDescriptor=controllerServices.GetActionSelector().SelectAction(controllerContext);</code>
<code> </code><code>HttpActionContextactionContext=newHttpActionContext(controllerContext, actionDescriptor);</code>
<code> </code><code>FilterGroupinggrouping=newFilterGrouping(actionDescriptor.GetFilterPipeline());</code>
<code> </code><code>IEnumerable<IActionFilter>actionFilters=grouping.ActionFilters;</code>
<code> </code><code>IEnumerable<IAuthorizationFilter>authorizationFilters=grouping.AuthorizationFilters;</code>
<code> </code><code>IEnumerable<IExceptionFilter>exceptionFilters=grouping.ExceptionFilters;</code>
<code> </code><code>returnInvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, ()=>actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>(</code><code>delegate</code> <code>{</code>
<code> </code><code>this</code><code>._modelState=actionContext.ModelState;</code>
<code> </code><code>returnInvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =>controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))();</code>
<code> </code><code>}, newCancellationToken(), </code><code>false</code><code>))(), actionContext, cancellationToken, exceptionFilters);</code>
代碼1-4中定義了控制器的執行過程,我們就從源碼的角度去了解一下控制器的執行過程。
在代碼1-4中先是從控制器上下文對象中擷取目前控制器類型的描述對象HttpControllerDescriptor類型的執行個體,而後從HttpControllerDescriptor類型執行個體從擷取在HttpConfiguration中的服務容器ServicesContainer類型的執行個體,對于這些類型前面的篇幅或多或少的講過了。
在這之後從服務容器中擷取IHttpActionSelector類型的行為選擇器并且經過篩選擷取到最佳比對的HttpActionDescriptor類型,在之前也有講到過HttpControllerDescriptor,這裡的HttpActionDescriptor跟其相似,就是表示控制其行為(方法)的中繼資料資訊。
下面我就來講解一下控制器行為選擇器的執行過程,也就是它篩選方法的幾個步驟。
首先我們要知道控制器行為選擇器的類型,從代碼1-4中可以看到是通過服務容器對象的擴充方法來擷取的,在前面的篇幅也都講過了,這裡可以得知我們要檢視的控制器行為選擇器的類型就是ApiControllerActionSelector類型。
ApiControllerActionSelector控制器行為選擇器
示例代碼1-5
16
17
18
19
20
21
22
23
<code> </code><code>publicclassApiControllerActionSelector : IHttpActionSelector</code>
<code> </code><code>//Fields</code>
<code> </code><code>privatereadonlyobject_cacheKey;</code>
<code> </code><code>privateActionSelectorCacheItem_fastCache;</code>
<code> </code><code>privateconststringActionRouteKey=</code><code>"action"</code><code>;</code>
<code> </code><code>privateconststringControllerRouteKey=</code><code>"controller"</code><code>;</code>
<code> </code><code>//Methods</code>
<code> </code><code>publicApiControllerActionSelector();</code>
<code> </code><code>publicvirtualILookup<</code><code>string</code><code>, HttpActionDescriptor>GetActionMapping(HttpControllerDescriptorcontrollerDescriptor);</code>
<code> </code><code>privateActionSelectorCacheItemGetInternalSelector(HttpControllerDescriptorcontrollerDescriptor);</code>
<code> </code><code>publicvirtualHttpActionDescriptorSelectAction(HttpControllerContextcontrollerContext);</code>
<code> </code><code>//Nested Types</code>
<code> </code><code>privateclassActionSelectorCacheItem</code>
<code> </code><code>{</code>
<code> </code><code>}</code>
<code> </code><code>privateclassLookupAdapter : ILookup<</code><code>string</code><code>, HttpActionDescriptor>, IEnumerable<IGrouping<</code><code>string</code><code>, HttpActionDescriptor>>, IEnumerable</code>
從代碼1-5中我們可以看到ApiControllerActionSelector類型中包含着兩個私有類,這兩個私有類後面會有講到起到的作用也很重要。
下面我們還是回到代碼1-4中的邏輯,從調用控制器行為選擇器中調用SelectAction()方法開始。
在ApiControllerActionSelector類型中調用SelectAction()時,實際是由SelectAction()方法調用GetInternalSelector()方法生成一個控制器方法的緩存對象,也就是ApiControllerActionSelector類型的私有類ActionSelectorCacheItem,而真正的篩選工作都是由它來執行的,是以下面才是介紹的重點。
控制器方法選擇器-篩選方法的步驟
1初始化篩選
在ActionSelectorCacheItem類型的初始化的時候, ActionSelectorCacheItem執行個體中會首先根據HttpControllerDescriptor對象擷取到控制器本身的類型,然後利用反射的技術根據條件擷取到目前控制器類型中的所有方法,最後儲存為MethodInfo[]。而所謂的條件就是(BindingFlags.Public 、BindingFlags.Instance、方法所屬類型必須是ApiController類型的)。
我們看下ActionSelectorCacheItem類型中的字段資訊,這些字段裡存放的都是很重要的資料,後面會一一說明。
示例代碼1-6
<code> </code><code>privatereadonlyReflectedHttpActionDescriptor[] _actionDescriptors;</code>
<code> </code><code>privatereadonlyILookup<</code><code>string</code><code>, ReflectedHttpActionDescriptor>_actionNameMapping;</code>
<code> </code><code>privatereadonlyIDictionary<ReflectedHttpActionDescriptor, </code><code>string</code><code>[]>_actionParameterNames=newDictionary<ReflectedHttpActionDescriptor, </code><code>string</code><code>[]>();</code>
<code> </code><code>privatereadonlyHttpMethod[] _cacheListVerbKinds=newHttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };</code>
<code> </code><code>privatereadonlyReflectedHttpActionDescriptor[][] _cacheListVerbs;</code>
<code> </code><code>privatereadonlyHttpControllerDescriptor_controllerDescriptor;</code>
1.1基礎資訊初始化-ReflectedHttpActionDescriptor[] _actionDescriptors
這個時候初始化工作并沒有做完,這時候會把MethodInfo[]數組中的每個MethodInfo執行個體封裝成ReflectedHttpActionDescriptor類型的對象,對于類型稍後再說。在封裝成ReflectedHttpActionDescriptor類型的對象後,也會将每個執行個體存至一個ReflectedHttpActionDescriptor類型的數組中。
1.2 基礎資訊初始化-IDictionary<ReflectedHttpActionDescriptor, string[]>_actionParameterNames
在1.1的工作做完之後呢,就會對每個ReflectedHttpActionDescriptor類型的對象進行分析,分析啥?分析方法的參數名稱,并且已1:n的方式存在IDictionary<ReflectedHttpActionDescriptor, string[]>類型的鍵值隊中。這裡存放的值就是一個方法描述對象作為key值,value值是這個方法的所有參數名稱。
1.3 基礎資訊初始化-ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping
這裡的工作是根據1.1工作的結果,利用_actionDescriptors變量來根據ActionName分組,而最後_actionNameMapping中的值也是集合類型,不過每一項中的值都是個1:n的鍵值隊值。因為控制器方法可能存在重載的情況。
1.4 基礎資訊初始化-ReflectedHttpActionDescriptor[][]_cacheListVerbs
_cacheListVerbs值的初始化在最後,它的定義是一個二維數組,數組初始确定為三行N列,三行的控制是由_cacheListVerbKinds值控制的,這裡初始化的是根據1.1工作的結果将_actionDescriptors值按Http方法類型進行分類,是以我說的是三行N列。
上面的這些步驟雖然有點煩,不過了解一下便于下面的了解。
2. Action名稱篩選
示例代碼1-7
<code>public</code> <code>HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)</code>
在ActionSelectorCacheItem類型的SelectAction()方法中,将會進行剩下的幾個篩選步驟,首先是會從方法的參數controllerContext中擷取到路由資料對象,并且在其Values屬性中查詢是否有“Action”鍵對應的方法名稱值,這個時候就會出現下面兩種情況。
2.1如果注冊的路由中有Action名稱
這這種情況下會把上面1.3中的工作成果拿來,_actionNameMapping根據Action名稱擷取到一個ReflectedHttpActionDescriptor類型的數組,這個數組在整個流程中還不能往下走,還要經過篩選,篩選的規則是判斷ReflectedHttpActionDescriptor中支援的Http方法類型是否支援目前請求的Http方法類型。
這裡就涉及到在ReflectedHttpActionDescriptor類型的内部對IActionHttpMethodProvider類型特性的處理,不多說後面的文章會講到。這裡上一張圖大家先留個印象。
圖1
2.2沒有路由名稱的根據Http方法類型
在這種情況下,則是根據代碼1-7中的方法的方法參數controllerContext中擷取目前的請求類型,然後從1.4的工作結果中用_cacheListVerbs值根據目前請求的Http方法類型擷取到ReflectedHttpActionDescriptor類型的數組執行個體。
3.根據請求參數名稱、數量來比對
3.1有參數的情況下
在這種情況下,會先把路由資料對象的Values屬性中的Keys值存放在一個集合A中,然後再擷取目前請求的查詢字元串集合,并且把集合中的所有Key值添加到集合A中,這就使的所有請求的參數名稱都在一個集合中,然後就會從1.2的結果中根據目前的ReflectedHttpActionDescriptor類型執行個體(這裡是接着2的流程,是以這裡是ReflectedHttpActionDescriptor類型的數組周遊執行)從_actionParameterNames擷取對應的參數名稱數組,然後是集合A會和擷取的參數名稱數組做一一的比較,看看集合A中是否包含參數名稱,如果都有了則是滿足條件。
這裡傳回的依然可能是ReflectedHttpActionDescriptor類型的數組,因為在一個方法有重載時,比如說Get(stringa)和Get(string a,string b)兩個方法時,請求中如果有a和b兩個參數的話,Get(string a)也是滿足條件的。
3.2無參數的情況下
這種情況下就比較簡單了,從1.2的結果中還如上述那般,周遊的根據ReflectedHttpActionDescriptor類型執行個體擷取參數名稱數組,找到數組長度為0的。
4. 排除IActionMethodSelector類型特性的控制器方法
到最後一個篩選條件了,還是周遊ReflectedHttpActionDescriptor類型數組中的每一項,并且查找他們是否有使用實作了IActionMethodSelector接口的特性。
4.1有使用了實作IActionMethodSelector接口的特性
在這種情況下,會擷取到IActionMethodSelector類型,并且調用其實作方法IsValidForRequest(),如果傳回true的話這個ReflectedHttpActionDescriptor類型才可以被使用,這也是提供給我們自定義實作的一個便捷,通常情況下是下面的這種情況。
4.2沒有使用實作IActionMethodSelector接口的特性
在這種情況下,會添加ReflectedHttpActionDescriptor類型到傳回執行個體的集合中。
最後控制器行為選擇器隻會傳回ReflectedHttpActionDescriptor類型集合的中的第一項且必須是隻有一項,其他情況都會抛出異常。
這個時候思緒回到代碼1-4,看到HttpActionDescriptor(ReflectedHttpActionDescriptor)類型變量被指派,回想下上面的過程,感覺過了好久一樣。
最後貼一下很粗略的示意圖
圖2
<a href="http://s3.51cto.com/wyfs02/M02/48/2C/wKiom1QF57biPx_uAAMGaQj_SgA368.jpg" target="_blank"></a>
我們上面所講的也不過隻是在整個過程中的第一步過程。
本文轉自jinyuan0829 51CTO部落格,原文連結http://blog.51cto.com/jinyuan/1548086:,如需轉載請自行聯系原作者