這篇隨筆講講路由功能,主要內容在項目Microsoft.AspNetCore.Routing中,能夠在GitHub上找到,Routing項目地址。html
路由功能是你們都很熟悉的功能,使用起來也十分簡單,從使用的角度來講可講的東西很少。不過閱讀源碼的過程的是個學習的過程,看看頂尖Coder怎麼組織代碼也是在提高本身。git
咱們知道如今ASP.NET Core中全部用到的功能都是服務,那麼Routing服務是何時被添加到依賴注入容器的呢?答案是在StartUp類的ConfigureServices方法中。若是咱們隨便新建的MVC 6的項目,在VS中模板會自動幫咱們添加一些代碼,在Startup類中的兩個方法咱們能夠找到一下代碼。github
public void ConfigureServices(IServiceCollection services) { // Add framework services. // 省略其餘框架服務
services.AddMvc();//添加MVC服務 // Add application services. //省略自定義應用服務
} public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
上面兩個StartUp類裏的方法,一個是註冊服務,一個使用服務。從它們調用的方法名上應該能區別出來,AddXXX的是註冊服務,UseXXX的是使用服務。這裏面用到的方法都是擴展方法,有關路由須要用到的服務都會在AddMvc()中註冊,固然這個方法還會註冊一大堆其餘MVC框架須要用的方法。若是想追蹤相關服務的添加語句的話:AddMvc()->AddMvcCore()->ConfigureDefaultServices()->AddRouting(),另一條線是AddMvc()->AddMvcCore()->AddMvcCoreServices()->TryAddSingleton<MvcDefaultHandler>()。前者是添加與路由模板解析存儲相關的服務,後者是處理請求路由的服務,包括請求路由與模板的配對,以及觸發相應的Action等等。app
我想分別從兩條線來解釋路由(Routing)的工做流程。框架
爲了解釋清楚相關概念,我想先解釋一下三個詞:Route, Routing, Router。三個詞均可以模糊地翻譯爲路由,可是這樣太容易混淆了,懂英語的人應該能一眼就看出其中的不一樣。async
註冊路由函數
首先從MapRoute()方法提及。這個方法會在內部調用這些代碼學習
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會在內部維護一個Route(s)的容器,上面的代碼在往容器裏面添加新的Route,若是咱們用VS默認的模板,那麼這裏面name="default", template="{controller=Home}/{action=Index}/{id?}",其餘參數諸如像defaults,constraints什麼的都是沒有的。須要說明的是此時routeBuilder.DefaultHandler已經被設置爲MvcDefaultHandler,這是在UseMvc()方法中被設置的。在Route類的構造過程當中(RouteBase的構造函數中),template字符串會被解析,包括是否是參數名(parameter)啊,是否是字面值(literal)啊,約束是什麼,默認值是什麼均可以被解析出來。MVC6的路由和MVC5有一點不同,默認值以及約束能夠寫在template裏,而MVC5的約束和默認值只能再傳遞一個匿名類型進去:defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }。顯然MVC6的路由簡單的多,固然你也能夠還用之前的方式傳遞默認值,沒有問題。MVC6的這種方法內置默認值和約束的方式在以前的特性路由已經體現了,用來改進傳統路由也在情理之中。MvcDefaultHandler會被賦給Route的_target字段,這個字段在將來請求來臨時發揮功效。ui
解析路由的過程就是一個字符串處理的過程,比較複雜,若是要所有講解篇幅太長。若是作過LeetCode上一些字符串處理的題目的話,看起來會輕鬆一些,有興趣的童鞋能夠去深究源碼。this
UseMvc()這個方法和路由有很大的關係,下面來看一下它的源碼
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( routes.DefaultHandler, app.ApplicationServices)); return app.UseRouter(routes.Build()); }
configureRoutes(routes)就是上面解釋的調用MapRoute()方法的一個Action委託。DefaultHandler在構造RouteBuilder時被設置爲默認的MvcRouteHandler,若是咱們想要使用其餘Handler,能夠模仿這個UseMvc()方法從新寫一個拓展方法,傳入你的Handler便可。注意下面那句代碼:它向RouteBuilder.Routes中添加了用於處理特性路由的Route,並用Insert方法將其添加了到容器的起始位置,這說明特性路由要優先於傳統路由。至於爲何要先添加傳統路由,是由於開發者能夠在傳入的configureRoutes這個委託中指定本身的Handler,DefaultHandler有可能在configureRoutes(routes)這段代碼中變了,因此特性路由的添加要晚於傳統路由。
RouteBuilder.Build()方法會生成一個包含當前Route的集合,這些Route攜帶了信息(包括傳統路由被解析的參數,約束等以及特性路由的元數據等),在上面的例子中,這個容器就兩個Route,一個特性路由,一個name="default"的傳統路由。最後app.UseRouter會向ApplicationBuilder中添加一個類型爲RouterMiddleware的中間件。此時整個有關路由的第一條工做流程就到此結束了。若是比較一下AddMvc()和UseMvc()會發現前一個方法關係到了很是多的服務,然後一個方法彷佛只用到了有關路由的東西,這是由於服務的註冊要一塊兒完成,而使用服務能夠即時拉取。當應用程序響應請求時,一開始只用到路由服務,假如請求匹配,纔會用到有關Controller和Action的服務,到那時候再拉取便可。
UseRouter()方法最終會調用ApplicationBuilder.Use()方法,RouterMiddleware的信息最終會以委託的方式存儲在ApplicationBuilder中,有關這方面的流程能夠參閱我以前的文章:Microsoft.AspNetCore.Hosting。
處理請求路由
上面說到咱們註冊路由時,路由的信息在UseRouter()方法調用時是以Func<RequesetDelegate, RequestDelegate>方式存在。每個中間件最初都是一種形態,利用這種方式,能夠把在程序中用到的中間件構形成一種委託鏈,最後能夠構造出一個跟使用順序有關的RequestDelegate:即請求管道。Routing是請求管道中的一部分,假如請求到達routing區域,則相關的RequestDelegate就會被觸發,利用反射構造出RouteMiddleware這個類,而後調用它的Invoke方法來處理有關路由的事務。
先來看看RouteMiddleware的Invoke方法。
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext);//構造一個路由上下文,三個屬性:HttpContext,Handler(一個委託),RouteData
context.RouteData.Routers.Add(_router); await _router.RouteAsync(context);//這裏的_router默認是MvcRouteHandler
if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; await context.Handler(context.HttpContext); } }
整個方法的邏輯就是:
接下來看一下MvcRouteHandler.RouteAsync()方法
1 public Task RouteAsync(RouteContext context) 2 { 3 //省略null檢查
4 var actionDescriptor = _actionSelector.Select(context);//關鍵!!找到最優的Action,並返回一個攜帶相關信息的數據類
5 if (actionDescriptor == null) 6 { 7 _logger.NoActionsMatched(); 8 return TaskCache.CompletedTask; 9 } 10 //省略action有默認值狀況的處理
11 context.Handler = (c) => InvokeActionAsync(c, actionDescriptor);//關鍵!!將RouteContext的Handler屬性置爲相應的處理方法
12 return TaskCache.CompletedTask; 13 } 14
15 private async Task InvokeActionAsync(HttpContext httpContext, ActionDescriptor actionDescriptor) 16 { 17 var routeData = httpContext.GetRouteData();//在RouteMiddleWare.Invoke()時候留下來的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);//根據相應的數據構造ActionContext上下文 29
30 //省略部分非主要邏輯代碼
31
32 var invoker = _actionInvokerFactory.CreateInvoker(actionContext);//構建一個有關Action處理的類型
33
34 await invoker.InvokeAsync();//觸發Action處理
35
36 _logger.ExecutedAction(actionDescriptor, startTimestamp); 37 } 38 } 39 finally
40 { 41 _diagnosticSource.AfterAction(actionDescriptor, httpContext, routeData); 42 } 43 }
註釋已經解釋的比較詳細了。經過RouteContext選出最優的ActionDescriptor,我一筆帶過了,不過這個過程與註冊路由時候的解析一下,比較複雜。涉及到決策樹之類的內容,感興趣的同窗能夠深究。若是確實有Action匹配的話,RouteContext.Handler會被設置爲相應的匿名方法。接着控制權交回給RouteMiddleware,而後觸發InvokeActionAsync()方法,RouteContext的使命就此結束。
從InvokeActionAsync()方法中能夠看出,框架根據相應的ActionDescriptor生成相應的ActionContext,以後進行有關Controller和Action的動做。撩完RouteContext就該輪到ActionContextle。
總結