ASP.NET Core MVC 源码学习:详解 Action 的匹配
前言
在 上一篇 文章中,我們已經(jīng)學(xué)習(xí)了 ASP.NET Core MVC 的啟動(dòng)流程,那么 MVC 在啟動(dòng)了之后,當(dāng)請(qǐng)求到達(dá)過(guò)來(lái)的時(shí)候,它是怎么樣處理的呢? 又是怎么樣把我們的請(qǐng)求準(zhǔn)確的傳達(dá)到我們的 Action 上呢? 那么,在這邊文章中,我們一起跟蹤源碼看一下,框架都做了些什么東西。
Getting Started
我們知道,Startup.cs 中的 Configure(IApplicationBuilder app) 中,我們使用 app.UseMvc()
在 UseMVC() 代碼執(zhí)行的過(guò)程中,它可以接收一個(gè) Action<IRouteBuilder> 形式的委托,我們使用這個(gè)委托可以進(jìn)行自定義路由的配置,默認(rèn)情況下,我們一般會(huì)如下進(jìn)行配置:
app.UseMvc(routes => {routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}"); });或者是你使用默認(rèn)的 app.UseMvcWithDefaultRoute(),這個(gè)擴(kuò)展方法在內(nèi)部已經(jīng)幫你做了上述代碼的內(nèi)容。
那我們今天就從這個(gè) Route 的配置開(kāi)始看起吧。
RouteContext 如何初始化?
在 IRouteBuilder 通過(guò)配置 IRouteBuilder,IRouteBuilder 在 Build() 之后會(huì)得到 Router 會(huì)得到 IRouter。
public static IApplicationBuilder UseMvc(this IApplicationBuilder app,Action<IRouteBuilder> configureRoutes) {// ......var routes = new RouteBuilder(app){DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),};configureRoutes(routes);routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));return app.UseRouter(routes.Build()); }上面的代碼有兩個(gè)地方需要注意的。
第一個(gè)地方是 DefaultHandler,可以看到默認(rèn)配置下,MVC 程序從 DI 中獲取 MvcRouteHandler 路由處理程序來(lái)作為路由的默認(rèn)處理程序。
第二個(gè)地方是 AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)
 ,那么這個(gè)地方是干嘛的呢?
CreateAttributeMegaRoute 它返回了一個(gè) IRouter ,主要是用來(lái)處理帶 RouteAttribute 標(biāo)記的 Action,我們來(lái)看一下這個(gè)方法:
public static IRouter CreateAttributeMegaRoute(IServiceProvider services) {return new AttributeRoute(services.GetRequiredService<IActionDescriptorCollectionProvider>(),services,actions => {var handler = services.GetRequiredService<MvcAttributeRouteHandler>();handler.Actions = actions;return handler;}); }在方法內(nèi)部,new 了一個(gè) AttributeRoute 返回了回去,大家可以看到有一個(gè)參數(shù) actions,它使用的是 MvcAttributeRouteHandler 這個(gè)處理程序,說(shuō)明在實(shí)際調(diào)用過(guò)程中使用的是 MvcAttributeRouteHandler 進(jìn)行的路由處理。
OK,我們總結(jié)一下關(guān)于 MVC 自己的幾個(gè)路由處理程序,還是用一個(gè)圖比較容易看的清楚,幸運(yùn)的是,MVC 一共就這3個(gè)路由處理程序,我們已經(jīng)全部接觸到了。
MVC 框架針對(duì)于 IRouter 接口的實(shí)現(xiàn)有以下三個(gè):
提前告訴你,最左邊綠色的那個(gè) AttributeRoute 其實(shí)只是一個(gè)包裝,在內(nèi)部也是通過(guò) MvcAttributeRouteHandler 或者 MvcRouteHandler 進(jìn)行的處理。那么,現(xiàn)在關(guān)于路由的處理程序只剩下了兩個(gè),他們分別是:
默認(rèn)處理程序: MvcRouteHandler,用來(lái)處理約定的 Action。
注解處理程序: MvcAttributeRouteHandler ,用來(lái)處理注解(Attribute)路由。
細(xì)心的同學(xué)可能注意到了, MvcAttributeRouteHandler 比 MvcRouteHandler 多了一個(gè) Actions : ActionDescriptor[]屬性。
我們?cè)倏匆幌逻@兩個(gè)處理程序的 RouteAsync 方法,這個(gè)方法是路由組件的入口方法,我們通過(guò)一個(gè)對(duì)比工具來(lái)看一下兩者之間的差距。
圖片看不清楚可以新標(biāo)簽打開(kāi)
可以看到,這兩個(gè) RouteAsync 主要有兩處差距,第一處就是 SelectBestCandidate 這個(gè)函數(shù)第二個(gè)參數(shù)
ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList<ActionDescriptor> candidates)MvcRouteHandler:
在這個(gè)流程中,顯示調(diào)用了 IActionSelect 接口中的 SelectCandidates() 用來(lái)找到所有符合條件的候選 Action,然后調(diào)用了 SelectBestCandidate 找出最佳的一個(gè)。
程序走到這里,這里會(huì)有兩個(gè)重點(diǎn)的地方,或者叫有疑問(wèn)的地方?
1、 程序集中定義的 Action 是怎么找到的?
要想找到程序定義的所有 Action,那么首先需要找到 Controller,在上一篇文章中我們已經(jīng)知道了有一個(gè) MVC 程序用來(lái)管理 AssemblyPart 的東西叫 ApplicationPartManager ,它的里面存儲(chǔ)了所有 MVC 框架在啟動(dòng)的時(shí)候加載的所有程序集,那么我們可以從這個(gè)程序集中找到需要的 Controller。下面這個(gè)流程圖顯示了查找Controller 的流程:
GetControllerTypes 返回的是一個(gè) IEnumerable<TypeInfo> 的集合,有了 Controller 之后,MVC 框架使用了一個(gè)對(duì)象來(lái)包裝 Controller,因?yàn)樵诤罄m(xù)的流程中,除了需要 Controller 之外還需要其他的一些東西,比如 Filter, ApiExplorer 等。
ApplicationModel
ApplicationModel 就是MVC框架用來(lái)包裝 Controller,Filter , ApiExplorer 等的一個(gè)Model 對(duì)象,我們來(lái)看一下它的定義:
public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel {public ApplicationModel(){ApiExplorer = new ApiExplorerModel();Controllers = new List<ControllerModel>();Filters = new List<IFilterMetadata>();Properties = new Dictionary<object, object>();}public ApiExplorerModel ApiExplorer { get; set; }public IList<ControllerModel> Controllers { get; private set; }public IList<IFilterMetadata> Filters { get; private set; }public IDictionary<object, object> Properties { get; } }ApplicationModel 里面關(guān)于 Controller 的包裝是一個(gè) IList<ControllerModel>,看一下 ControllerModel 的定義:
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel {//......public IList<ActionModel> Actions { get; }public ApiExplorerModel ApiExplorer { get; set; }public ApplicationModel Application { get; set; }public IReadOnlyList<object> Attributes { get; }MemberInfo ICommonModel.MemberInfo => ControllerType;string ICommonModel.Name => ControllerName;public string ControllerName { get; set; }public TypeInfo ControllerType { get; }public IList<PropertyModel> ControllerProperties { get; }public IList<IFilterMetadata> Filters { get; }public IDictionary<string, string> RouteValues { get; }public IDictionary<object, object> Properties { get; }public IList<SelectorModel> Selectors { get; } }在 ASP.NET Core MVC 框架中,ApplicationModel 有下面幾個(gè)提供者,他們用于初始化整個(gè) ApplicationModel 的各個(gè)部分,我們還是分別看一下吧。
AuthorizationApplicationModelProvider: 處理認(rèn)證相關(guān)業(yè)務(wù)邏輯,在它的Executing方法中會(huì)將 AuthorizeFilter,AllowAnonymousFilter 等過(guò)濾器添加到 ApplicationModelProviderContext 里面的 ApplicationModel 里。
DefaultApplicationModelProvider:初始化 ControllerModel, 添加 Controller 相關(guān)的各種信息,添加用戶自定義 Filter,遍歷 ControllerTypes : 創(chuàng)建 ControllerModel --> 初始化Properties --> 初始化Parameters。
CorsApplicationModelProvider:跨域資源相關(guān)邏輯,添加CorsAuthorizationFilterFactory,DisableCorsAuthorizationFilter,CorsAuthorizationFilterFactory,DisableCorsAuthorizationFilter等過(guò)濾器。
TempDataApplicationModelProvider: 添加 SaveTempDataPropertyFilterFactory 過(guò)濾器,存儲(chǔ)Controller中的TempData信息,注意 TempDataAttribute 修飾的屬性只能是基元類(lèi)型或字符串。
構(gòu)建ApplicationModel
MVC 框架通過(guò) ControllerActionDescriptorProvider 中的 BuildModel() 這個(gè)方法進(jìn)行 ApplicationModel 的構(gòu)建:
internal protected ApplicationModel BuildModel() {var controllerTypes = GetControllerTypes();var context = new ApplicationModelProviderContext(controllerTypes);for (var i = 0; i < _applicationModelProviders.Length; i++){_applicationModelProviders[i].OnProvidersExecuting(context);}for (var i = _applicationModelProviders.Length - 1; i >= 0; i--){_applicationModelProviders[i].OnProvidersExecuted(context);}return context.Result; }現(xiàn)在,我們已經(jīng)有一個(gè)完整的 ApplicationModel 對(duì)象了。
有了 ApplicationModel 對(duì)象之后,會(huì)再進(jìn)行一次約定的應(yīng)用。比如以下Action重寫(xiě)路由的情況或者配置多個(gè)路由的情況。
2、ActionDescriptorCollection是怎么創(chuàng)建的?
ControllerActionDescriptor構(gòu)建
ControllerActionDescriptor的構(gòu)建是基于ApplicationModel對(duì)象的,下面我就畫(huà)了一個(gè)流程圖用來(lái)展示構(gòu)建 ControllerActionDescriptor 的整個(gè)過(guò)程,就不過(guò)多描述了。
截止到目前,我們會(huì)得到一個(gè) IEnumerable<ControllerActionDescriptor> 集合對(duì)象。
在有了 ControllerActionDescriptor 之后,ActionDescriptorCollectionProvider 會(huì)提供一個(gè)屬性,
public ActionDescriptorCollection ActionDescriptors {get{if (_collection == null){UpdateCollection();}return _collection;} }在這個(gè)屬性中使用了 UpdateCollection 這個(gè)方法來(lái)更新 ActionDescriptorCollection。
private void UpdateCollection() {var context = new ActionDescriptorProviderContext();for (var i = 0; i < _actionDescriptorProviders.Length; i++){_actionDescriptorProviders[i].OnProvidersExecuting(context);}for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--){_actionDescriptorProviders[i].OnProvidersExecuted(context);}_collection = new ActionDescriptorCollection(new ReadOnlyCollection<ActionDescriptor>(context.Results),Interlocked.Increment(ref _version)); }OK , 現(xiàn)在我們有了 ActionDescriptorCollection , 之后的流程就比較簡(jiǎn)單了,但是會(huì)涉及到幾個(gè)算法。
接下來(lái),輪到 ActionSelectorDecisionTreeProvider 上場(chǎng)了,它主要是把 ActionDescriptorCollection,組裝成為一個(gè) IActionSelectionDecisionTree 對(duì)象以便于后續(xù)的查找匹配工作, IActionSelectionDecisionTree 的數(shù)據(jù)結(jié)構(gòu)是一個(gè)多叉樹(shù),組裝過(guò)程是使用了一個(gè)深度優(yōu)先的遞歸算法。
我們回到起點(diǎn),繼續(xù)看這張圖:
現(xiàn)在 SelectCandidates 你應(yīng)該能夠看懂了:
public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context) {//IActionSelectionDecisionTree 對(duì)象var tree = _decisionTreeProvider.DecisionTree;//使用的是一個(gè)多叉樹(shù)查找算法,關(guān)于算法可以看我這篇博文://http://www.cnblogs.com/savorboard/p/6582399.htmlreturn tree.Select(context.RouteData.Values); }接下來(lái)就是 SelectBestCandidates 這個(gè)流程:
1、遍歷 Action 列表,評(píng)估 Action 的相關(guān)約束,返回匹配的 ActionDescriptor 列表。
2、從匹配的 ActionDescriptor 列表中返回最佳的 Action 列表,注意這里這個(gè)方法 SelectBestActions,它是一個(gè)虛方法,默認(rèn)是沒(méi)有實(shí)現(xiàn)的會(huì)直接返回上一步的結(jié)果,也就是說(shuō)用戶可以通過(guò)重寫(xiě)這個(gè)方法來(lái)自定義一些Action匹配規(guī)則。
3、如果SelectBestActions 返回的是一個(gè)ActionDescriptor,則直接返回,當(dāng)路由系統(tǒng)匹配到多個(gè) Action 的時(shí)候,那么 MVC 需要從這些 Action 候選者中選中最佳的哪一個(gè),當(dāng)兩個(gè)動(dòng)作通過(guò)路由匹配時(shí),MVC必須消除歧義以選擇“最佳”候選者,否則拋出 AmbiguousActionException 異常
最終 SelectBestCandidates 會(huì)返回一個(gè) ActionDescriptor ,即需要執(zhí)行的 Action。
后續(xù)流程的執(zhí)行,我又畫(huà)了一個(gè)圖來(lái)表示,希望能夠更加清晰一些:
終于講解結(jié)束了,心好累,如果你認(rèn)為本篇文章對(duì)你有幫助的話,順手點(diǎn)個(gè)【推薦】吧。
MvcAttributeRouteHandler:
下面是MvcAttributeRouteHandler的 RouteAsync。
可以看到,在 MvcAttributeRouteHandler 中,少了 SelectCandidates() 這個(gè)流程,取而代之的是用 Actions 的屬性參數(shù)。 這個(gè)Actions 就比較簡(jiǎn)單了,就是MVC框架啟動(dòng)的時(shí)候配置的IRouter Action。
然后就是 SelectBestCandidates 這個(gè)流程了,參考上文的流程吧,都一樣。
總結(jié)
本文詳細(xì)描述了 MVC 在 Request 到達(dá)的時(shí)候是怎么樣通過(guò)自定義的路由處理程序來(lái)選擇一個(gè)Action 的,并且講解了其中的過(guò)程。
如果你對(duì) .NET Core 感興趣可以關(guān)注我,我會(huì)定期在博客分享關(guān)于 .NET Core 的學(xué)習(xí)心得,如果你認(rèn)為本篇文章對(duì)你有幫助的話,謝謝你的【推薦】。
本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html
 作者博客:Savorboard
 歡迎轉(zhuǎn)載,請(qǐng)?jiān)诿黠@位置給出出處及鏈接
轉(zhuǎn)載于:https://www.cnblogs.com/savorboard/p/aspnetcore-mvc-routing-action.html
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core MVC 源码学习:详解 Action 的匹配的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 阿里云的服务器内网互通的前提条件
- 下一篇: C# DataTable的Select(
