ASP.NET Web API 控制器執行過程(一)c#
前言數組
前面兩篇講解了控制器的建立過程,只是從框架源碼的角度去簡單的瞭解,在控制器建立事後所執行的過程也是尤其重要的,本篇就來簡單的說明一下控制器在建立事後將會作哪些工做。緩存
ASP.NET Web API 控制器執行過程app
l ASP.NET Web API 控制器執行過程(一)框架
l ASP.NET Web API 控制器執行過程(二)ide
咱們知道控制器的生成過程都是在HttpControllerDispatcher類型中來操做的,那咱們要想知道控制器在建立事後執行操做的入口點也必須在HttpControllerDispatcher類型中才能發現。來看以下示例代碼:this
代碼1-1spa
privateTask<HttpResponseMessage>SendAsyncInternal(HttpRequestMessagerequest, CancellationTokencancellationToken) { IHttpRouteDatarouteData=request.GetRouteData(); HttpControllerDescriptordescriptor=this.ControllerSelector.SelectController(request); IHttpControllercontroller=descriptor.CreateController(request); HttpConfigurationconfiguration=request.GetConfiguration(); HttpControllerContextcontrollerContext=newHttpControllerContext(descriptor.Configuration, routeData, request) { Controller=controller, ControllerDescriptor=descriptor }; returncontroller.ExecuteAsync(controllerContext, cancellationToken); }
看過前面兩篇的朋友看到這裏的代碼必定會很熟悉了,控制器的生成過程就包含在了其中,在代碼1-1中咱們會看到HttpControllerContext類型,從它的名稱來看想必你們也都知道了它的做用,表明着進入控制器處理階段的邏輯上的上下文對象,而且封裝着一些很重要的信息。對象
HttpControllerContext控制器上下文blog
示例代碼1-2
publicclassHttpControllerContext { publicHttpControllerContext(); publicHttpControllerContext(HttpConfigurationconfiguration, IHttpRouteDatarouteData, HttpRequestMessagerequest); publicHttpConfigurationConfiguration { get; set; } publicIHttpControllerController { get; set; } publicHttpControllerDescriptorControllerDescriptor { get; set; } publicHttpRequestMessageRequest { get; set; } publicIHttpRouteDataRouteData { get; set; } }
結合代碼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
publicabstractclassApiController : IHttpController, IDisposable { publicvirtualTask<System.Net.Http.HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken); }
在代碼1-3中咱們能夠看到再ApiController類型中定義了ExecuteAsync()方法,ApiController爲抽象類型,控制器的主要執行過程也就是都在ExecuteAsync()方法中,下面我看一下具體的實現,以下示例代碼。
代碼1-4
publicvirtualTask<HttpResponseMessage>ExecuteAsync(HttpControllerContextcontrollerContext, CancellationTokencancellationToken) { HttpControllerDescriptorcontrollerDescriptor=controllerContext.ControllerDescriptor; ServicesContainercontrollerServices=controllerDescriptor.Configuration.Services; HttpActionDescriptoractionDescriptor=controllerServices.GetActionSelector().SelectAction(controllerContext); HttpActionContextactionContext=newHttpActionContext(controllerContext, actionDescriptor); FilterGroupinggrouping=newFilterGrouping(actionDescriptor.GetFilterPipeline()); IEnumerable<IActionFilter>actionFilters=grouping.ActionFilters; IEnumerable<IAuthorizationFilter>authorizationFilters=grouping.AuthorizationFilters; IEnumerable<IExceptionFilter>exceptionFilters=grouping.ExceptionFilters; returnInvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, ()=>actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>(delegate { this._modelState=actionContext.ModelState; returnInvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =>controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))(); }, newCancellationToken(), false))(), actionContext, cancellationToken, exceptionFilters); }
代碼1-4中定義了控制器的執行過程,咱們就從源碼的角度去了解一下控制器的執行過程。
在代碼1-4中先是從控制器上下文對象中獲取當前控制器類型的描述對象HttpControllerDescriptor類型的實例,然後從HttpControllerDescriptor類型實例從獲取在HttpConfiguration中的服務容器ServicesContainer類型的實例,對於這些類型前面的篇幅或多或少的講過了。
在這以後從服務容器中獲取IHttpActionSelector類型的行爲選擇器而且通過篩選獲取到最佳匹配的HttpActionDescriptor類型,在以前也有講到過HttpControllerDescriptor,這裏的HttpActionDescriptor跟其類似,就是表示控制其行爲(方法)的元數據信息。
下面我就來說解一下控制器行爲選擇器的執行過程,也就是它篩選方法的幾個步驟。
首先咱們要知道控制器行爲選擇器的類型,從代碼1-4中能夠看到是經過服務容器對象的擴展方法來獲取的,在前面的篇幅也都講過了,這裏能夠得知咱們要查看的控制器行爲選擇器的類型就是ApiControllerActionSelector類型。
ApiControllerActionSelector控制器行爲選擇器
示例代碼1-5
publicclassApiControllerActionSelector : IHttpActionSelector { //Fields privatereadonlyobject_cacheKey; privateActionSelectorCacheItem_fastCache; privateconststringActionRouteKey="action"; privateconststringControllerRouteKey="controller"; //Methods publicApiControllerActionSelector(); publicvirtualILookup<string, HttpActionDescriptor>GetActionMapping(HttpControllerDescriptorcontrollerDescriptor); privateActionSelectorCacheItemGetInternalSelector(HttpControllerDescriptorcontrollerDescriptor); publicvirtualHttpActionDescriptorSelectAction(HttpControllerContextcontrollerContext); //Nested Types privateclassActionSelectorCacheItem { } privateclassLookupAdapter : ILookup<string, HttpActionDescriptor>, IEnumerable<IGrouping<string, HttpActionDescriptor>>, IEnumerable { } }
從代碼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
privatereadonlyReflectedHttpActionDescriptor[] _actionDescriptors; privatereadonlyILookup<string, ReflectedHttpActionDescriptor>_actionNameMapping; privatereadonlyIDictionary<ReflectedHttpActionDescriptor, string[]>_actionParameterNames=newDictionary<ReflectedHttpActionDescriptor, string[]>(); privatereadonlyHttpMethod[] _cacheListVerbKinds=newHttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post }; privatereadonlyReflectedHttpActionDescriptor[][] _cacheListVerbs; privatereadonlyHttpControllerDescriptor_controllerDescriptor;
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
public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
在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
咱們上面所講的也不過只是在整個過程當中的第一步過程。