有過ASP.NET或其它現代Web框架開發經歷的開發者對路由這一名字應該不陌生。若是要用一句話解釋什麼是路由,能夠這樣形容:經過對URL的解析,指定相應的處理程序。html
回憶下在Web Forms應用程序中使用路由的方式:node
public static void RegisterRoutes(RouteCollection routes) { routes.MapPageRoute("", "Category/{action}/{categoryName}", "~/categoriespage.aspx"); }
而後是MVC應用程序:app
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" } ); }
再到了ASP.NET Core:框架
public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
還能夠用更簡單的寫法:async
public void Configure(IApplicationBuilder app) { app.UseMvcWithDefaultRoute(); }
從源碼上看這兩個方法的實現是同樣的。ide
public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
關鍵是內部UseMvc方法的內容:ui
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()); }
其中的處理過程,首先實例化了一個RouteBuilder對象,並對它的DefaultHandler屬性賦值爲MvcRouteHandler。接着以其爲參數,執行routes.MapRoute方法。this
MapRoute的處理過程就是爲RouteBuilder裏的Routes集合新增一個Route對象。spa
public static IRouteBuilder MapRoute( this IRouteBuilder routeBuilder, string name, string template, object defaults, object constraints, object dataTokens) { ... var inlineConstraintResolver = routeBuilder .ServiceProvider .GetRequiredService<IInlineConstraintResolver>(); routeBuilder.Routes.Add(new Route( routeBuilder.DefaultHandler, name, template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens), inlineConstraintResolver)); return routeBuilder; }
有此一個Route對象仍不夠,程序裏又插入了一個AttributeRoute。日誌
隨後執行routes.Build(),返回RouteCollection集合。該集合實現了IRouter接口。
public IRouter Build() { var routeCollection = new RouteCollection(); foreach (var route in Routes) { routeCollection.Add(route); } return routeCollection; }
最終使用已完成配置的路由。
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router) { ... return builder.UseMiddleware<RouterMiddleware>(router); }
因而又看到了熟悉的Middleware。它的核心方法裏先調用了RouteCollection的RouteAsync處理。
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router); await _router.RouteAsync(context); 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); } }
其內部又依次執行各個Route的RouteAsync方法。
public async virtual Task RouteAsync(RouteContext context) { ... for (var i = 0; i < Count; i++) { var route = this[i]; context.RouteData.Routers.Add(route); try { await route.RouteAsync(context); if (context.Handler != null) { break; } } ... } }
以前的邏輯中分別在RouteCollection里加入了AttributeRoute與Route。
*循環中會判斷Handler是否被賦值,這是爲了不在路由已被匹配的狀況下,繼續進行其它的匹配。從執行順序來看,很容易明白AttributeRoute比通常Route優先級高的道理。
先執行AttributeRoute裏的RouteAsync方法:
public Task RouteAsync(RouteContext context) { var router = GetTreeRouter(); return router.RouteAsync(context); }
裏面調用了TreeRouter的RouteAsync方法:
public async Task RouteAsync(RouteContext context) { foreach (var tree in _trees) { var tokenizer = new PathTokenizer(context.HttpContext.Request.Path); var root = tree.Root; var treeEnumerator = new TreeEnumerator(root, tokenizer); ... while (treeEnumerator.MoveNext()) { var node = treeEnumerator.Current; foreach (var item in node.Matches) { var entry = item.Entry; var matcher = item.TemplateMatcher; try { if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values)) { continue; } if (!RouteConstraintMatcher.Match( entry.Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { continue; } _logger.MatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText); context.RouteData.Routers.Add(entry.Handler); await entry.Handler.RouteAsync(context); if (context.Handler != null) { return; } } ... } } } }
若是全部AttributeRoute路由都不能匹配,則不會進一步做處理。不然的話,將繼續執行Handler中的RouteAsync方法。這裏的Handler是MvcAttributeRouteHandler。
public Task RouteAsync(RouteContext context) { ... var actionDescriptor = _actionSelector.SelectBestCandidate(context, Actions); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } foreach (var kvp in actionDescriptor.RouteValues) { if (!string.IsNullOrEmpty(kvp.Value)) { context.RouteData.Values[kvp.Key] = kvp.Value; } } context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } return invoker.InvokeAsync(); }; return Task.CompletedTask; }
該方法內部的處理僅是爲RouteContext的Handler屬性賦值。實際的操做則是要到RouterMiddleware中Invoke方法的context.Handler(context.HttpContext)
這一步才被執行的。
至於Route裏的RouteAsync方法:
public virtual Task RouteAsync(RouteContext context) { ... EnsureMatcher(); EnsureLoggers(context.HttpContext); var requestPath = context.HttpContext.Request.Path; if (!_matcher.TryMatch(requestPath, context.RouteData.Values)) { // If we got back a null value set, that means the URI did not match return Task.CompletedTask; } // Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all // created lazily. if (DataTokens.Count > 0) { MergeValues(context.RouteData.DataTokens, DataTokens); } if (!RouteConstraintMatcher.Match( Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { return Task.CompletedTask; } _logger.MatchedRoute(Name, ParsedTemplate.TemplateText); return OnRouteMatched(context); }
只有路由被匹配的時候纔在OnRouteMatched裏調用target的RouteAsync方法。
protected override Task OnRouteMatched(RouteContext context) { context.RouteData.Routers.Add(_target); return _target.RouteAsync(context); }
此處的target便是最初建立RouteBuilder時傳入的MvcRouteHandler。
public Task RouteAsync(RouteContext context) { ... var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } return invoker.InvokeAsync(); }; return Task.CompletedTask; }
處理過程與MvcAttributeRouteHandler類似,同樣是要在RouterMiddleware的Invoke裏才執行Handler的方法。
以一張思惟導圖能夠簡單歸納上述的過程。
或者用三句話也能夠描述整個流程。