有过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. SpringCloud分布式事务TCC实现

    可以参考 http://www.txlcn.org/ 的实现方式

  2. sublime text3全局设置

    1.快捷键 ctrl+shift+p 2.输入 PackageResourceViewer 3.找到后 Open Resource 打开  , 4.选择 Theme-default,鼠标双击Theme ...

  3. shell脚本死循环检查是否有特定的路由,若存在进行删除操作

    while [ 1 ] do tun0_route=`ip route |grep -ci "100.100.80.0"` if [ $tun0_route -eq 0 ];the ...

  4. sql server 2008 express 安装的时提示“重启计算机失败"

    sql server 2008 express 安装的时提示"重启计算机失败" 解决办法: 打开注册表编辑器(regedit.exe),在HKEY_LOCAL_MACHINE\SY ...

  5. SNF开发平台WinForm-审核流使用方法样例

    一.效果如下: 二.如何实现 1.程序的数据表设计规范,参考<09.SNF-C#编程规范V1.5.docx>文件. 2.程序操作程序 2.1.在程序页面拖拽控件 2.2.程序的Load事件 ...

  6. Oracle 创建分区表

    --查看数据库中所有用户的分区表 SELECT * FROM DBA_TABLES WHERE PARTITIONED='YES' AND OWNER NOT IN ('SYSTEM','SYS') ...

  7. Unity读取Excel文件(附源代码)

    今天想弄个Unity读取Excel的功能的,发现网上有许多方法,采用其中一种方法:加入库文件 Excel.dll 和ICSharpCode.SharpZipLib.dll库文件,(还有System.D ...

  8. 【Git】简单使用

    [Git & Github] 首先不能混淆两者的概念.git是一个类似于svn的版本管理工具.其可以在本地建立起针对一个项目的众多维度的版本管理体系,提升了开发的效率. 相对的,我们如果想要和 ...

  9. 接口app 接口中上传 图片

    /** * @Method base64图片上传 * @author 黄国金 * return array * date 2016-1-10 */function saveBase64Image($b ...

  10. iLBC

    iLBC是一种专为包交换网络通信设计的编解码,优于目前流行的G.729.G.723.1,对丢包进行了特有处理,即使在丢包率 相当高的网络环境下,仍可获得非常清晰的语音效果.