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

來源:互聯網
上載者:User

標籤:asp.net web api   apicontroller   

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

前面兩篇講解了控制器的建立過程,只是從架構源碼的角度去簡單的瞭解,在控制器建立過後所執行的過程也是尤為重要的,本篇就來簡單的說明一下控制器在建立過後將會做哪些工作。

 

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

我們知道控制器的產生過程都是在HttpControllerDispatcher類型中來操作的,那我們要想知道控制器在建立過後執行操作的進入點也必須在HttpControllerDispatcher類型中才能發現。來看如下範例程式碼:

代碼1-1

    private Task<HttpResponseMessage> SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)    {        IHttpRouteData routeData = request.GetRouteData();        HttpControllerDescriptor descriptor = this.ControllerSelector.SelectController(request);             IHttpController controller = descriptor.CreateController(request);            HttpConfiguration configuration = request.GetConfiguration();        HttpControllerContext controllerContext = new HttpControllerContext(descriptor.Configuration, routeData, request) {            Controller = controller,            ControllerDescriptor = descriptor        };        return controller.ExecuteAsync(controllerContext, cancellationToken);}

看過前面兩篇的朋友看到這裡的代碼一定會很熟悉了,控制器的產生過程就包含在了其中,在代碼1-1中我們會看到HttpControllerContext類型,從它的名稱來看想必大家也都知道了它的作用,代表著進入控制器處理階段的邏輯上的內容物件,並且封裝著一些很重要的資訊。

 

HttpControllerContext控制器上下文

範例程式碼1-2

    public class HttpControllerContext    {        public HttpControllerContext();        public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request);        public HttpConfiguration Configuration { get; set; }        public IHttpController Controller { get; set; }        public HttpControllerDescriptor ControllerDescriptor { get; set; }        public HttpRequestMessage Request { get; set; }        public IHttpRouteData RouteData { 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

    public abstract class ApiController : IHttpController, IDisposable    {        public virtual Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);    }

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

代碼1-4

    public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)    {        HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;        ServicesContainer controllerServices = controllerDescriptor.Configuration.Services;        HttpActionDescriptor actionDescriptor = controllerServices.GetActionSelector().SelectAction(controllerContext);        HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);        FilterGrouping grouping = new FilterGrouping(actionDescriptor.GetFilterPipeline());        IEnumerable<IActionFilter> actionFilters = grouping.ActionFilters;        IEnumerable<IAuthorizationFilter> authorizationFilters = grouping.AuthorizationFilters;        IEnumerable<IExceptionFilter> exceptionFilters = grouping.ExceptionFilters;        return InvokeActionWithExceptionFilters(InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, () => actionDescriptor.ActionBinding.ExecuteBindingAsync(actionContext, cancellationToken).Then<HttpResponseMessage>(delegate {            this._modelState = actionContext.ModelState;            return InvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () => controllerServices.GetActionInvoker().InvokeActionAsync(actionContext, cancellationToken))();        }, new CancellationToken(), false))(), actionContext, cancellationToken, exceptionFilters);   }

代碼1-4中定義了控制器的執行過程,我們就從源碼的角度去瞭解一下控制器的執行過程。

在代碼1-4中先是從控制器內容物件中擷取當前控制器類型的描述對象HttpControllerDescriptor類型的執行個體,而後從HttpControllerDescriptor類型執行個體從擷取在HttpConfiguration中的服務容器ServicesContainer類型的執行個體,對於這些類型前面的篇幅或多或少的講過了。

在這之後從服務容器中擷取IHttpActionSelector類型的行為選取器並且經過篩選擷取到首選的HttpActionDescriptor類型,在之前也有講到過HttpControllerDescriptor,這裡的HttpActionDescriptor跟其相似,就是表示控制其行為(方法)的中繼資料資訊。

 

下面我就來講解一下控制器行為選取器的執行過程,也就是它篩選方法的幾個步驟。

首先我們要知道控制器行為選取器的類型,從代碼1-4中可以看到是通過服務容器物件的擴充方法來擷取的,在前面的篇幅也都講過了,這裡可以得知我們要查看的控制器行為選取器的類型就是ApiControllerActionSelector類型。

 

ApiControllerActionSelector控制器行為選取器

範例程式碼1-5

    public class ApiControllerActionSelector : IHttpActionSelector    {        // Fields        private readonly object _cacheKey;        private ActionSelectorCacheItem _fastCache;        private const string ActionRouteKey = "action";        private const string ControllerRouteKey = "controller";        // Methods        public ApiControllerActionSelector();        public virtual ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);        private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor);        public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);        // Nested Types        private class ActionSelectorCacheItem        {        }        private class LookupAdapter : 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

        private readonly ReflectedHttpActionDescriptor[] _actionDescriptors;        private readonly ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping;        private readonly IDictionary<ReflectedHttpActionDescriptor, string[]> _actionParameterNames = new Dictionary<ReflectedHttpActionDescriptor, string[]>();        private readonly HttpMethod[] _cacheListVerbKinds = new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };        private readonly ReflectedHttpActionDescriptor[][] _cacheListVerbs;        private readonly HttpControllerDescriptor _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(string a)和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

金源

出處:http://blog.csdn.net/jinyuan0829

本文著作權歸作者和CSDN共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.