有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生。如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序。
回忆下在Web Forms应用程序中使用路由的方式:
public static void RegisterRoutes(RouteCollection routes){
routes.MapPageRoute("", "Category/{action}/{categoryName}", "~/categoriespage.aspx");
}
然后是MVC应用程序:
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?}");
});
}
还可以用更简单的写法:
public void Configure(IApplicationBuilder app){
app.UseMvcWithDefaultRoute();
}
从源码上看这两个方法的实现是一样的。
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方法的内容:
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方法。
MapRoute的处理过程就是为RouteBuilder里的Routes集合新增一个Route对象。
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的方法。
以一张思维导图可以简单概括上述的过程。

或者用三句话也可以描述整个流程。