天天看點

ASP.NET Web API 控制器執行過程(一)

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&lt;HttpResponseMessage&gt;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&lt;System.Net.Http.HttpResponseMessage&gt;ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken);</code>

在代碼1-3中我們可以看到再ApiController類型中定義了ExecuteAsync()方法,ApiController為抽象類型,控制器的主要執行過程也就是都在ExecuteAsync()方法中,下面我看一下具體的實作,如下示例代碼。

代碼1-4

13

14

15

<code>publicvirtualTask&lt;HttpResponseMessage&gt;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&lt;IActionFilter&gt;actionFilters=grouping.ActionFilters;</code>

<code>        </code><code>IEnumerable&lt;IAuthorizationFilter&gt;authorizationFilters=grouping.AuthorizationFilters;</code>

<code>        </code><code>IEnumerable&lt;IExceptionFilter&gt;exceptionFilters=grouping.ExceptionFilters;</code>

<code>        </code><code>returnInvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, ()=&gt;actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then&lt;HttpResponseMessage&gt;(</code><code>delegate</code> <code>{</code>

<code>            </code><code>this</code><code>._modelState=actionContext.ModelState;</code>

<code>            </code><code>returnInvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =&gt;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&lt;</code><code>string</code><code>, HttpActionDescriptor&gt;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&lt;</code><code>string</code><code>, HttpActionDescriptor&gt;, IEnumerable&lt;IGrouping&lt;</code><code>string</code><code>, HttpActionDescriptor&gt;&gt;, 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&lt;</code><code>string</code><code>, ReflectedHttpActionDescriptor&gt;_actionNameMapping;</code>

<code>        </code><code>privatereadonlyIDictionary&lt;ReflectedHttpActionDescriptor, </code><code>string</code><code>[]&gt;_actionParameterNames=newDictionary&lt;ReflectedHttpActionDescriptor, </code><code>string</code><code>[]&gt;();</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&lt;ReflectedHttpActionDescriptor, string[]&gt;_actionParameterNames

在1.1的工作做完之後呢,就會對每個ReflectedHttpActionDescriptor類型的對象進行分析,分析啥?分析方法的參數名稱,并且已1:n的方式存在IDictionary&lt;ReflectedHttpActionDescriptor, string[]&gt;類型的鍵值隊中。這裡存放的值就是一個方法描述對象作為key值,value值是這個方法的所有參數名稱。

1.3 基礎資訊初始化-ILookup&lt;string, ReflectedHttpActionDescriptor&gt; _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:,如需轉載請自行聯系原作者

繼續閱讀