ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不大,但从内部运行方式上来说,差别还是很大的。上一篇详细介绍了原版路由方案的运行机制,本文仍然通过一幅图来了解一下新版的运行机制,最后再总结一下二者的异同点。(ASP.NET Core 系列目录

一、概述

此方案从2.2版本开始,被称作终结点路由(下文以“新版”称呼),它是默认开启的,若想采用原来的方案(<=2.1,下文以原版称呼),可以在AddMvc的时候进行设置

services.AddMvc(option=>option.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

EnableEndpointRouting 默认为true,也就是启用新的Endpoint方案,设置为false则采用旧版(<=2.1)的路由方案。

在配置方法上来说,系统仍然采用在Startup中的use.Mvc()中配置,而实际上内部的处理中间件已由原来的RouterMiddleware改为EndpointMiddleware和EndpointRoutingMiddleware两个中间件处理,下面依旧通过一幅图来详细看一下:

二、流程及解析

图一

为了方便查看,依然对几个“重点对象”做了颜色标识(点击图片可以看大图):

      1. 路由的初始化配置(图的前两个泳道) 

    ①  一切依然是从Startup开始,而且和旧版一样,是通过UseMvc方法进行配置,传入routes.MapRoute(...)这样的一个或多个配置, 不做赘述。
    下面着重说一下后面的流程,看一下MvcApplicationBuilderExtensions中的UseMvc方法:
 public static IApplicationBuilder UseMvc(
this IApplicationBuilder app,
Action<IRouteBuilder> configureRoutes)
{
//此处各种验证,略。。
var options = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
if (options.Value.EnableEndpointRouting)
{
var mvcEndpointDataSource = app.ApplicationServices
.GetRequiredService<IEnumerable<EndpointDataSource>>()
.OfType<MvcEndpointDataSource>()
.First();
var parameterPolicyFactory = app.ApplicationServices
.GetRequiredService<ParameterPolicyFactory>(); var endpointRouteBuilder = new EndpointRouteBuilder(app); configureRoutes(endpointRouteBuilder); foreach (var router in endpointRouteBuilder.Routes)
{
// Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint
// Sub-types could have additional customization that we can't knowingly convert
if (router is Route route && router.GetType() == typeof(Route))
{
var endpointInfo = new MvcEndpointInfo(
route.Name,
route.RouteTemplate,
route.Defaults,
route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value),
route.DataTokens,
parameterPolicyFactory);
mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
}
else
{
throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Endpoint Routing.");
}
}
if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _))
{
// Matching middleware has not been registered yet
// For back-compat register middleware so an endpoint is matched and then immediately used
app.UseEndpointRouting();
}
return app.UseEndpoint();
}
else
{
//旧版路由方案
}
}

② 第6行,这里会获取并判断设置的EnableEndpointRouting的值,若为false,则采用旧版路由,详见上一篇文章;该值默认为true,即采用新版路由。
            ③ 对应第9行,MvcEndpointDataSource在新版路由中是个非常非常重要的角色,在启动初始化阶段,它完成了路由表存储和转换,此处先用颜色重点标记一下,大家记住它,在后面的流程中详细介绍。
            ④ 对应第16行,同旧版的RouteBuilder一样,这里会new一个 endpointRouteBuilder,二者都是一个IRouteBuilder,所以也同样调用configureRoutes(endpointRouteBuilder)方法(也就是startup中的配置)获取了一个Route的集合(IList<IRouter>)赋值给endpointRouteBuilder.Routes,这里有个特别该注意的地方if (router is Route route && router.GetType() == typeof(Route)) ,也就是这里只接受route类型,终结点路由系统不支持基于 IRouter的可扩展性,包括从 Route继承。
            ⑤ 对应第20行,这里对刚获取到的endpointRouteBuilder.Routes进行遍历,转换成了一个MvcEndpointInfo的集和,赋值给mvcEndpointDataSource.ConventionalEndpointInfos。
            ⑥ 之后就是向管道塞中间件了,这里的处理中间件由原来的RouterMiddleware改为EndpointMiddleware和EndpointRoutingMiddleware。

       2.请求的处理(图的后两个泳道)

请求的处理大部分功能在中间件EndpointRoutingMiddleware,他有个重要的属性_endpointDataSource保存了上文中初始化阶段生成的MvcEndpointDataSource,而中间件EndpointMiddleware的功能比较简单,主要是在EndpointRoutingMiddleware筛选出endpoint之后,调用该endpoint的endpoint.RequestDelegate(httpContext)进行请求处理。
            ⑦ InitializeAsync()方法主要是用于调用InitializeCoreAsync()创建一个matcher,而通过这个方法的代码可以看出它只是在第一次请求的时候执行一次。

private Task<Matcher> InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
} return InitializeCoreAsync();
}

⑧ MvcEndpointDataSource一个重要的方法UpdateEndpoints(),作用是读取所有action,并将这个action列表与它的ConventionalEndpointInfos列表(见⑤)进行匹配,最终生成一个新的列表。如下图,我们默认情况下只配置了一个"{controller=Home}/{action=Index}/{id?}"这样的路由,默认的HomeController有三个action,添加了一个名为FlyLoloController的controller并添加了一个带属性路由的action,最终生成了7个Endpoint,这有点像路由与action的“乘积”。当然,这里只是用默认程序举了个简单的例子,实际项目中可能会有更多的路由模板注册、会有更多的Controller和Action以及属性路由等。

图二

具体代码如下:

         private void UpdateEndpoints()
{
lock (_lock)
{
var endpoints = new List<Endpoint>();
StringBuilder patternStringBuilder = null; foreach (var action in _actions.ActionDescriptors.Items)
{
if (action.AttributeRouteInfo == null)
{
// In traditional conventional routing setup, the routes defined by a user have a static order
// defined by how they are added into the list. We would like to maintain the same order when building
// up the endpoints too.
//
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
var conventionalRouteOrder = ; // Check each of the conventional patterns to see if the action would be reachable
// If the action and pattern are compatible then create an endpoint with the
// area/controller/action parameter parts replaced with literals
//
// e.g. {controller}/{action} with HomeController.Index and HomeController.Login
// would result in endpoints:
// - Home/Index
// - Home/Login
foreach (var endpointInfo in ConventionalEndpointInfos)
{
// An 'endpointInfo' is applicable if:
// 1. it has a parameter (or default value) for 'required' non-null route value
// 2. it does not have a parameter (or default value) for 'required' null route value
var isApplicable = true;
foreach (var routeKey in action.RouteValues.Keys)
{
if (!MatchRouteValue(action, endpointInfo, routeKey))
{
isApplicable = false;
break;
}
} if (!isApplicable)
{
continue;
} conventionalRouteOrder = CreateEndpoints(
endpoints,
ref patternStringBuilder,
action,
conventionalRouteOrder,
endpointInfo.ParsedPattern,
endpointInfo.MergedDefaults,
endpointInfo.Defaults,
endpointInfo.Name,
endpointInfo.DataTokens,
endpointInfo.ParameterPolicies,
suppressLinkGeneration: false,
suppressPathMatching: false);
}
}
else
{
var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); CreateEndpoints(
endpoints,
ref patternStringBuilder,
action,
action.AttributeRouteInfo.Order,
attributeRoutePattern,
attributeRoutePattern.Defaults,
nonInlineDefaults: null,
action.AttributeRouteInfo.Name,
dataTokens: null,
allParameterPolicies: null,
action.AttributeRouteInfo.SuppressLinkGeneration,
action.AttributeRouteInfo.SuppressPathMatching);
}
} // See comments in DefaultActionDescriptorCollectionProvider. These steps are done
// in a specific order to ensure callers always see a consistent state. // Step 1 - capture old token
var oldCancellationTokenSource = _cancellationTokenSource; // Step 2 - update endpoints
_endpoints = endpoints; // Step 3 - create new change token
_cancellationTokenSource = new CancellationTokenSource();
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); // Step 4 - trigger old token
oldCancellationTokenSource?.Cancel();
}
}

本质就是计算出一个个可能被请求的请求终结点,也就是Endpoint。由此可见,如上一篇文章那样想自定义一个handler来处理特殊模板的方式(如 routes.MapRoute("flylolo/{code}/{name}", MyRouteHandler.Handler);)将被忽略掉,因其无法生成 Endpoint,且此种方式完全可以自定义一个中间件来实现,没必要混在路由中。

⑨ 就是用上面生成的Matcher,携带Endpoint列表与请求URL做匹配,并将匹配到的Endpoint赋值给feature.Endpoint。
            ⑩ 获取feature.Endpoint,若存在则调用其RequestDelegate处理请求httpContext。

三、新版与旧版的异同点总结

简要从应用系统启动和请求处理两个阶段对比说一下两个版本的区别:

1.启动阶段:

这个阶段大部分都差不多,都是通过Startup的app.UseMvc()方法配置一个路由表,一个Route的集合Routes(IList<IRouter>),然后将其简单转换一下

<=2.1:  将Routes转换为RouteCollection

2.2+ :   将Routes转换为List<MvcEndpointInfo>

二者区别不大,虽然名字不同,但本质上还是差不多,都仍可理解为Route的集合的包装。

2.请求处理阶段:

<=2.1:   1. 将请求的URL与RouteCollection中记录的路由模板进行匹配。

2. 找到匹配的Route之后,再根据这个请求的URL判断是否存在对应的Controlled和Action。

3. 若以上均通过,则调用Route的Handler对HttpContext进行处理。

2.2+ :   1. 第一次处理请求时,首先根据启动阶段所配置的路由集合List<MvcEndpointInfo>和_actions.ActionDescriptors.Items(所有的action的信息)做匹配,生成一个列表,这个列表存储了所有可能被匹配的URL模板,如图二,这个列表同样是List<MvcEndpointInfo>,记录了所有可能的URL模式,实际上是列出了一个个可以被访问的详细地址,已经算是最终地址了,即终结点,或许就是为什么叫Endpoint路由的原因。

2.请求的Url和这个生成的表做匹配,找到对应的MvcEndpointInfo。

3. 调用被匹配的MvcEndpointInfo的RequestDelegate方法对请求进行处理。

二者区别就是对于_actions.ActionDescriptors.Items(所有的action的信息)的匹配上,原版是先根据路由模板匹配后,再根据ActionDescriptors判断是否存在对应的Controller和action,而新版是先利用了action信息与路由模板匹配,然后再用请求的URL进行匹配,由于这样的工作只在第一次请求的时候执行,所以虽然没有做执行效率上的测试,但感觉应该是比之前快的。

ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案的更多相关文章

  1. ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案 try.dot.net 的正确使用姿势 .Net NPOI 根据excel模板导出excel、直接生成excel .Net NPOI 上传excel文件、提交后台获取excel里的数据

    ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案   ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不 ...

  2. ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案

    原文:ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案 ASP.NET Core 从2.2版本开始,采用了一个新的名为Endpoint的路由方案,与原来的方案在使用上差别不大 ...

  3. ExpandoObject与DynamicObject的使用 RabbitMQ与.net core(一)安装 RabbitMQ与.net core(二)Producer与Exchange ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler) .NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

    ExpandoObject与DynamicObject的使用   using ImpromptuInterface; using System; using System.Dynamic; names ...

  4. ASP.NET Core 2.2 十九. 你扔过来个json,我怎么接

    原文:ASP.NET Core 2.2 十九. 你扔过来个json,我怎么接 前文说道了Action的激活,这里有个关键的操作就是Action参数的映射与模型绑定,这里即涉及到简单的string.in ...

  5. 学习ASP.NET Core Blazor编程系列六——新增图书(上)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  6. 创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段

    创建ASP.NET Core MVC应用程序(5)-添加查询功能 & 新字段 添加查询功能 本文将实现通过Name查询用户信息. 首先更新GetAll方法以启用查询: public async ...

  7. ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序

    ASP.NET core 的Filter是系统中经常用到的,本文详细分享一下各种Filter定义.执行的内部机制以及执行顺序.(ASP.NET Core 系列目录) 一. 概述 ASP.NET Cor ...

  8. 学习ASP.NET Core Razor 编程系列六——数据库初始化

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. ASP.NET Core 2.2 十九. Action参数的映射与模型绑定

    前文说道了Action的激活,这里有个关键的操作就是Action参数的映射与模型绑定,这里即涉及到简单的string.int等类型,也包含Json等复杂类型,本文详细分享一下这一过程.(ASP.NET ...

随机推荐

  1. LDAP概念和原理介绍

    LDAP概念和原理介绍 相信对于许多的朋友来说,可能听说过LDAP,但是实际中对LDAP的了解和具体的原理可能还比较模糊,今天就从“什么是LDAP”.“LDAP的主要产品”.“LDAP的基本模型”.“ ...

  2. Java数据结构和算法 - 简单排序

    Q: 冒泡排序? A: 1) 比较相邻的元素.如果第一个比第二个大,就交换它们两个; 2) 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数; 3) 针 ...

  3. 利用 vue-cli 构建一个 Vue 项目

    一.项目初始构建 现在如果要构建一个 Vue 的项目,最方便的方式,莫过于使用官方的 vue-cli . 首先,咱们先来全局安装 vue-cli ,打开命令行工具,输入以下命令: $ npm inst ...

  4. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之三 || Swagger的使用 3.1

    常见问题 1.经常有小伙伴遇到这个错误 这是因为接口json文档定义和调用不是一个 1.定义: ConfigureServices 方法中的  services.AddSwaggerGen 注册的一个 ...

  5. .net core自定义高性能的Web API服务网关

    网关对于服务起到一个统一控制处理的作用,也便于客户端更好的调用:通过网关可以灵活地控制服务应用接口负载,故障迁移,安全控制,监控跟踪和日志处理等.由于网关在性能和可靠性上都要求非常严格,所以针对业务需 ...

  6. Linux用户和权限管理看了你就会用啦

    前言 只有光头才能变强 回顾前面: 看完这篇Linux基本的操作就会了 没想到上一篇能在知乎获得千赞呀,Linux也快期末考试了,也有半个月没有写文章了.这篇主要将Linux下的用户和权限知识点再整理 ...

  7. vue-resource实现数据的绑定、添加、删除

    vue-resource实现数据的绑定.添加.删除 <!DOCTYPE html> <html lang="en"> <head> <ti ...

  8. DSAPI WIN7风格

    在Winform项目中,有时需要将UI变成适应Vista/Windows7/8/10的风格,通过"选用"以下代码来使你的UI支持系统主题渲染. 注:该功能不支持XP,建议使用DS控 ...

  9. layUI框架中文件上传前后端交互及遇到的相关问题

    下面我将讲述一下我在使用layUI框架中文件上传所遇到的问题: 前端jsp页面: <div class="layui-form-item"> <label cla ...

  10. SpringCloud-Eureka注册与发现

    这两年可以说微服务是热词,也是新领域,学习的成本也较高,基础东西太多比如什么Dubbo,zookeeper,Springboot等等.Dubbo也是实现服务治理又是阿里出的一套开源RPC框架,但是Sp ...