.NET Core开发日志——简述路由
有过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的方法。
以一张思维导图可以简单概括上述的过程。

或者用三句话也可以描述整个流程。
- 添加路由
- 匹配地址
- 处理请求
.NET Core开发日志——简述路由的更多相关文章
- .NET Core开发日志——Entity Framework与PostgreSQL
Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...
- .NET Core开发日志——RequestDelegate
本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...
- C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志
C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...
- .NET Core开发日志——从搭建开发环境开始
.NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...
- .NET Core开发日志——OData
简述 OData,即Open Data Protocol,是由微软在2007年推出的一款开放协议,旨在通过简单.标准的方式创建和使用查询式及交互式RESTful API. 类库 在.NET Core中 ...
- .NET Core开发日志——结构化日志
在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...
- .NET Core开发日志——Edge.js
最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...
- .NET Core开发日志——Linux版本的SQL Server
SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL ...
- .NET Core开发日志——Filter
ASP.NET Core MVC中的Filter作用是在请求处理管道的某些阶段之前或之后可以运行特定的代码. Filter特性在之前的ASP.NET MVC中已经出现,但过去只有Authorizati ...
随机推荐
- 《Unix&Linux大学教程》学习笔记三:Shell常识
1:全局变量与局部变量 全局:可以从父进程传递给子进程的变量,如:环境变量. 局部:只能在特定的子Shell中使用的变量. 局部变量变全局:使用 “export 局部” 指令将创建的局部变量导出到环境 ...
- xcode 报错 malloc: *** error for object 0x6c3c5a4: incorrect checksum for freed object - object was probably modified after being freed. *** set a breakpoint in malloc_error_break to debug------d
大家有时候会遇到这个错误 malloc: *** error for object 0x******: incorrect checksum for freed object - object was ...
- 将csv的数据导入mysql
手头有一份8MB的CSV文件需要分析,对于程序员来说,还有比在数据库里分析更愉快的事情吗? 所以让我们把CSV导入MYSQL吧. 一.首先按照文件列数创建相应的SQL表 例如: DROP TABLE ...
- Fluent UDF【8】:编译型UDF
UDF除了可以以解释的方式外,其还可以以编译的方式被Fluent加载.解释型UDF只能使用部分C语言功能,而编译型UDF则可以全面使用C语言的所有功能. 1 编译型UDF介绍 编译型UDF的构建方式与 ...
- https://stackoverflow.com/questions/51751426/failed-to-run-the-da-platform-trial-vm
https://stackoverflow.com/questions/51751426/failed-to-run-the-da-platform-trial-vm { "annotat ...
- linux每日命令(1):ls命令
ls命令是linux下最常用的命令.ls命令就是list的缩写缺省下ls用来打印出当前目录的清单如果ls指定其他目录那么就会显示指定目录里的文件及文件夹清单. 通过ls 命令不仅可以查看linux ...
- 推荐几个Windows工具软件: Stickies - 桌面贴
主页: http://www.zhornsoftware.co.uk/stickies/index.html Stickies work like Post-it notes for your PC. ...
- VMware Workstation Pro 14.1.1 正式版
VMware是功能最强大的虚拟机软件,用户可以在虚拟机同时运行各种操作系统,进行开发.测试.演示和部署软件,虚拟机中复制服务器.台式机和平板环境,每个虚拟机可分配多个处理器核心.主内存和显存. 更新日 ...
- Leaflet API翻译
转自: http://jsrookie.iteye.com/blog/2318972(上) http://jsrookie.iteye.com/blog/2318973(下) L.Map API各种类 ...
- FPGA编程—组合逻辑编码器等verilog实现
本篇博客主要实现对组合逻辑电路的一些常用模块的实现.组合逻辑中,包括译码器,编码器,输入输出选择器,数值比较器,算法单元等. 先来实现编码器,最常用的8-3编码器,这里先讲一下要用到的case ,c ...