有过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开发日志——简述路由的更多相关文章

  1. .NET Core开发日志——Entity Framework与PostgreSQL

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  2. .NET Core开发日志——RequestDelegate

    本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...

  3. 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 重点: 实现多级子目录的压缩, ...

  4. .NET Core开发日志——从搭建开发环境开始

    .NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...

  5. .NET Core开发日志——OData

    简述 OData,即Open Data Protocol,是由微软在2007年推出的一款开放协议,旨在通过简单.标准的方式创建和使用查询式及交互式RESTful API. 类库 在.NET Core中 ...

  6. .NET Core开发日志——结构化日志

    在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...

  7. .NET Core开发日志——Edge.js

    最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...

  8. .NET Core开发日志——Linux版本的SQL Server

    SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL ...

  9. .NET Core开发日志——Filter

    ASP.NET Core MVC中的Filter作用是在请求处理管道的某些阶段之前或之后可以运行特定的代码. Filter特性在之前的ASP.NET MVC中已经出现,但过去只有Authorizati ...

随机推荐

  1. MySql之插入操作

    一:插入一行数据 INSERT INTO tableName(列名...) VALUES(对应列名的值); 二:插入多行 INSERT INTO tableName(列名...) VALUES(对应列 ...

  2. MapReduce实现矩阵乘法

    简单回想一下矩阵乘法: 矩阵乘法要求左矩阵的列数与右矩阵的行数相等.m×n的矩阵A,与n×p的矩阵B相乘,结果为m×p的矩阵C.具体内容能够查看:矩阵乘法. 为了方便描写叙述,先进行如果: 矩阵A的行 ...

  3. FOR XML PATH 可以将查询结果根据行输出成XML格式

    SELECT CAST(OrderID AS varchar)+',' as OrderNo FROM Product CAST函数用于将某种数据类型的表达式显式转换为另一种数据类型 SELECT C ...

  4. 每天一个linux命令(8):scp使用

    不同的Linux之间copy文件常用有3种方法:1.使用ftp,也就是其中一台Linux安装ftp Server,这样可以另外一台使用ftp的client程序来进行文件的copy.2.采用samba服 ...

  5. 我的Android进阶之旅------&gt;Android 关于arm64-v8a、armeabi-v7a、armeabi、x86下的so文件兼容问题

    Android 设备的CPU类型通常称为ABIs 问题描写叙述 解决方法 1解决之前的截图 2解决后的截图 3解决方法 4建议 为什么你须要重点关注so文件 App中可能出错的地方 其它地方也可能出错 ...

  6. Git应用实践(二)

    [时间:2017-08] [状态:Open] [关键词:Git,git diff, git apply, git format-patch, git am, git log] 0-背景 距上次总结Gi ...

  7. Test Design Techniques - STATE BASED TESTING

    Test Design Techniques - STATE BASED TESTING -Test note of “Essential Software Test Design” 2015-08- ...

  8. egret3.x升级5.2

    第一步 先用新建项目向导新建一个5.2的项目 第二步 把3.x的代码和资源文件复制过来替换掉 修改资源加载代码 在3.x里 egret采用的是事件机制来加载资源,在5.2里则采用了await/asyn ...

  9. Java知多少(100)图像处理基础

    图像是由一组像素构成,用二进制形式保存的图片.java语言支持GIF.JPEG和BMP这3种主要图像文件格式.java语言的图像处理功能被封装在Image类中. 图像载入和输出 在java程序中,图像 ...

  10. Best Practices in Asynchronous Programming

    http://blog.stephencleary.com/ http://blogs.msdn.com/b/pfxteam/