ASP.NET Core 源码阅读笔记(5) ---Microsoft.AspNetCore.Routing路由
??? 這篇隨筆講講路由功能,主要內(nèi)容在項(xiàng)目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing項(xiàng)目地址。
? ? 路由功能是大家都很熟悉的功能,使用起來也十分簡(jiǎn)單,從使用的角度來說可講的東西不多。不過閱讀源碼的過程的是個(gè)學(xué)習(xí)的過程,看看頂尖Coder怎么組織代碼也是在提升自己。
??? 我們知道現(xiàn)在ASP.NET Core中所有用到的功能都是服務(wù),那么Routing服務(wù)是什么時(shí)候被添加到依賴注入容器的呢?答案是在StartUp類的ConfigureServices方法中。如果我們隨便新建的MVC 6的項(xiàng)目,在VS中模板會(huì)自動(dòng)幫我們添加一些代碼,在Startup類中的兩個(gè)方法我們可以找到一下代碼。
public void ConfigureServices(IServiceCollection services){// Add framework services.// 省略其他框架服務(wù)services.AddMvc();//添加MVC服務(wù)// Add application services.//省略自定義應(yīng)用服務(wù) }public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){app.UseMvc(routes =>{routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");});}???? 上面兩個(gè)StartUp類里的方法,一個(gè)是注冊(cè)服務(wù),一個(gè)使用服務(wù)。從它們調(diào)用的方法名上應(yīng)該能區(qū)別出來,AddXXX的是注冊(cè)服務(wù),UseXXX的是使用服務(wù)。這里面用到的方法都是擴(kuò)展方法,有關(guān)路由需要用到的服務(wù)都會(huì)在AddMvc()中注冊(cè),當(dāng)然這個(gè)方法還會(huì)注冊(cè)一大堆其他MVC框架需要用的方法。如果想追蹤相關(guān)服務(wù)的添加語句的話:AddMvc()->AddMvcCore()->ConfigureDefaultServices()->AddRouting(),另外一條線是AddMvc()->AddMvcCore()->AddMvcCoreServices()->TryAddSingleton<MvcDefaultHandler>()。前者是添加與路由模板解析存儲(chǔ)相關(guān)的服務(wù),后者是處理請(qǐng)求路由的服務(wù),包括請(qǐng)求路由與模板的配對(duì),以及觸發(fā)相應(yīng)的Action等等。
?????我想分別從兩條線來解釋路由(Routing)的工作流程。
???? 為了解釋清楚相關(guān)概念,我想先解釋一下三個(gè)詞:Route, Routing, Router。三個(gè)詞都可以模糊地翻譯為路由,但是這樣太容易混淆了,懂英語的人應(yīng)該能一眼就看出其中的不同。
- Routing是統(tǒng)稱,有關(guān)路由的總體概念,就可以以Routing來描述,比如我講的這個(gè)項(xiàng)目就叫Routing,或者我們有個(gè)添加有關(guān)路由服務(wù)的方法,就會(huì)叫AddRouting();
- Route是用來描述某一個(gè)特定路由的名詞,它有具體的數(shù)據(jù)項(xiàng),比如說上面的代碼我們向框架中添加了一個(gè)name是default的Route;它還是一個(gè)類名,這個(gè)類就是干Route應(yīng)該干的事情:存儲(chǔ)、解析數(shù)據(jù)之類的;
- Router可以翻譯為“路由者”帶路黨,是一種Handler的體現(xiàn),用來處理請(qǐng)求的路由,比如MVC框架默認(rèn)的MvcRouteHandler就是一個(gè)Router。
注冊(cè)路由
???? 首先從MapRoute()方法說起。這個(gè)方法會(huì)在內(nèi)部調(diào)用這些代碼
1 routeBuilder.Routes.Add(new Route( 2 routeBuilder.DefaultHandler, 3 name, 4 template, 5 new RouteValueDictionary(defaults), 6 new RouteValueDictionary(constraints), 7 new RouteValueDictionary(dataTokens), 8 inlineConstraintResolver));???? RouteBuilder會(huì)在內(nèi)部維護(hù)一個(gè)Route(s)的容器,上面的代碼在往容器里面添加新的Route,如果我們用VS默認(rèn)的模板,那么這里面name="default", template="{controller=Home}/{action=Index}/{id?}",其他參數(shù)諸如像defaults,constraints什么的都是沒有的。需要說明的是此時(shí)routeBuilder.DefaultHandler已經(jīng)被設(shè)置為MvcDefaultHandler,這是在UseMvc()方法中被設(shè)置的。在Route類的構(gòu)造過程中(RouteBase的構(gòu)造函數(shù)中),template字符串會(huì)被解析,包括是不是參數(shù)名(parameter)啊,是不是字面值(literal)啊,約束是什么,默認(rèn)值是什么都可以被解析出來。MVC6的路由和MVC5有一點(diǎn)不一樣,默認(rèn)值以及約束可以寫在template里,而MVC5的約束和默認(rèn)值只能再傳遞一個(gè)匿名類型進(jìn)去:defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }。顯然MVC6的路由簡(jiǎn)單的多,當(dāng)然你也可以還用以前的方式傳遞默認(rèn)值,沒有問題。MVC6的這種方法內(nèi)置默認(rèn)值和約束的方式在之前的特性路由已經(jīng)體現(xiàn)了,用來改進(jìn)傳統(tǒng)路由也在情理之中。MvcDefaultHandler會(huì)被賦給Route的_target字段,這個(gè)字段在未來請(qǐng)求來臨時(shí)發(fā)揮功效。
???? 解析路由的過程就是一個(gè)字符串處理的過程,比較復(fù)雜,如果要全部講解篇幅太長(zhǎng)。如果做過LeetCode上一些字符串處理的題目的話,看起來會(huì)輕松一些,有興趣的童鞋可以去深究源碼。
???? UseMvc()這個(gè)方法和路由有很大的關(guān)系,下面來看一下它的源碼
public static IApplicationBuilder UseMvc(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes){//省略了檢查參數(shù)的代碼...var routes = new RouteBuilder(app){DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),};configureRoutes(routes);routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(routes.DefaultHandler,app.ApplicationServices));return app.UseRouter(routes.Build());}???? configureRoutes(routes)就是上面解釋的調(diào)用MapRoute()方法的一個(gè)Action委托。DefaultHandler在構(gòu)造RouteBuilder時(shí)被設(shè)置為默認(rèn)的MvcRouteHandler,如果我們想要使用其他Handler,可以模仿這個(gè)UseMvc()方法重新寫一個(gè)拓展方法,傳入你的Handler即可。注意下面那句代碼:它向RouteBuilder.Routes中添加了用于處理特性路由的Route,并用Insert方法將其添加了到容器的起始位置,這說明特性路由要優(yōu)先于傳統(tǒng)路由。至于為什么要先添加傳統(tǒng)路由,是因?yàn)殚_發(fā)者可以在傳入的configureRoutes這個(gè)委托中指定自己的Handler,DefaultHandler有可能在configureRoutes(routes)這段代碼中變了,所以特性路由的添加要晚于傳統(tǒng)路由。
???? RouteBuilder.Build()方法會(huì)生成一個(gè)包含當(dāng)前Route的集合,這些Route攜帶了信息(包括傳統(tǒng)路由被解析的參數(shù),約束等以及特性路由的元數(shù)據(jù)等),在上面的例子中,這個(gè)容器就兩個(gè)Route,一個(gè)特性路由,一個(gè)name="default"的傳統(tǒng)路由。最后app.UseRouter會(huì)向ApplicationBuilder中添加一個(gè)類型為RouterMiddleware的中間件。此時(shí)整個(gè)有關(guān)路由的第一條工作流程就到此結(jié)束了。如果比較一下AddMvc()和UseMvc()會(huì)發(fā)現(xiàn)前一個(gè)方法關(guān)系到了非常多的服務(wù),而后一個(gè)方法似乎只用到了有關(guān)路由的東西,這是因?yàn)榉?wù)的注冊(cè)要一起完成,而使用服務(wù)可以即時(shí)拉取。當(dāng)應(yīng)用程序響應(yīng)請(qǐng)求時(shí),一開始只用到路由服務(wù),假如請(qǐng)求匹配,才會(huì)用到有關(guān)Controller和Action的服務(wù),到那時(shí)候再拉取即可。
? ? ?UseRouter()方法最終會(huì)調(diào)用ApplicationBuilder.Use()方法,RouterMiddleware的信息最終會(huì)以委托的方式存儲(chǔ)在ApplicationBuilder中,有關(guān)這方面的流程可以參閱我之前的文章:Microsoft.AspNetCore.Hosting。
處理請(qǐng)求路由
??? 上面說到我們注冊(cè)路由時(shí),路由的信息在UseRouter()方法調(diào)用時(shí)是以Func<RequesetDelegate, RequestDelegate>方式存在。每一個(gè)中間件最初都是一種形態(tài),利用這種方式,可以把在程序中用到的中間件構(gòu)造成一種委托鏈,最后可以構(gòu)造出一個(gè)跟使用順序有關(guān)的RequestDelegate:即請(qǐng)求管道。Routing是請(qǐng)求管道中的一部分,假如請(qǐng)求到達(dá)routing區(qū)域,則相關(guān)的RequestDelegate就會(huì)被觸發(fā),利用反射構(gòu)造出RouteMiddleware這個(gè)類,然后調(diào)用它的Invoke方法來處理有關(guān)路由的事務(wù)。
??? 先來看看RouteMiddleware的Invoke方法。
public async Task Invoke(HttpContext httpContext){var context = new RouteContext(httpContext);//構(gòu)造一個(gè)路由上下文,三個(gè)屬性:HttpContext,Handler(一個(gè)委托),RouteData context.RouteData.Routers.Add(_router);await _router.RouteAsync(context);//這里的_router默認(rèn)是MvcRouteHandlerif (context.Handler == null){_logger.RequestDidNotMatchRoutes();await _next.Invoke(httpContext);}else{httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature(){RouteData = context.RouteData,};await context.Handler(context.HttpContext);}}??? 整個(gè)方法的邏輯就是:
??? 接下來看一下MvcRouteHandler.RouteAsync()方法
1 public Task RouteAsync(RouteContext context) 2 { 3 //省略null檢查 4 var actionDescriptor = _actionSelector.Select(context);//關(guān)鍵!!找到最優(yōu)的Action,并返回一個(gè)攜帶相關(guān)信息的數(shù)據(jù)類 5 if (actionDescriptor == null) 6 { 7 _logger.NoActionsMatched(); 8 return TaskCache.CompletedTask; 9 } 10 //省略action有默認(rèn)值情況的處理 11 context.Handler = (c) => InvokeActionAsync(c, actionDescriptor);//關(guān)鍵!!將RouteContext的Handler屬性置為相應(yīng)的處理方法 12 return TaskCache.CompletedTask; 13 } 14 15 private async Task InvokeActionAsync(HttpContext httpContext, ActionDescriptor actionDescriptor) 16 { 17 var routeData = httpContext.GetRouteData();//在RouteMiddleWare.Invoke()時(shí)候留下來的RouteData 18 try 19 { 20 _diagnosticSource.BeforeAction(actionDescriptor, httpContext, routeData); 21 22 using (_logger.ActionScope(actionDescriptor))//日志記錄 23 { 24 _logger.ExecutingAction(actionDescriptor); 25 26 var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : 0; 27 28 var actionContext = new ActionContext(httpContext, routeData, actionDescriptor);//根據(jù)相應(yīng)的數(shù)據(jù)構(gòu)造ActionContext上下文 29 30 //省略部分非主要邏輯代碼 31 32 var invoker = _actionInvokerFactory.CreateInvoker(actionContext);//構(gòu)建一個(gè)有關(guān)Action處理的類型 33 34 await invoker.InvokeAsync();//觸發(fā)Action處理 35 36 _logger.ExecutedAction(actionDescriptor, startTimestamp); 37 } 38 } 39 finally 40 { 41 _diagnosticSource.AfterAction(actionDescriptor, httpContext, routeData); 42 } 43 }??? 注釋已經(jīng)解釋的比較詳細(xì)了。通過RouteContext選出最優(yōu)的ActionDescriptor,我一筆帶過了,不過這個(gè)過程與注冊(cè)路由時(shí)候的解析一下,比較復(fù)雜。涉及到?jīng)Q策樹之類的內(nèi)容,感興趣的同學(xué)可以深究。如果確實(shí)有Action匹配的話,RouteContext.Handler會(huì)被設(shè)置為相應(yīng)的匿名方法。接著控制權(quán)交回給RouteMiddleware,然后觸發(fā)InvokeActionAsync()方法,RouteContext的使命就此結(jié)束。
??? 從InvokeActionAsync()方法中可以看出,框架根據(jù)相應(yīng)的ActionDescriptor生成相應(yīng)的ActionContext,之后進(jìn)行有關(guān)Controller和Action的動(dòng)作。撩完RouteContext就該輪到ActionContextle。
總結(jié)
轉(zhuǎn)載于:https://www.cnblogs.com/bill-shooting/p/5562066.html
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 源码阅读笔记(5) ---Microsoft.AspNetCore.Routing路由的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原创:长平之战中,廉颇早就埋下了战败的种
- 下一篇: asp.net等项目编译失败的原因之不能