有過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的方法。
以一張思維導圖可以簡單概括上述的過程。
或者用三句話也可以描述整個流程。
- 添加路由
- 匹配地址
- 處理請求