到目前为止,ASP.NET Core提供了两种不同的路由解决方案。传统的路由系统以IRouter对象为核心,我们姑且将其称为IRouter路由。本章介绍的是最早发布于ASP.NET Core 2.2中的新路由系统,由于它采用基于终结点映射的策略,所以我们将其称为终结点路由。终结点路由自然以终结点为核心,所以先介绍终结点在路由系统中的表现形式。[更多关于ASP.NET Core的文章请点这里]

之所以将应用划分为若干不同的终结点,是因为不同的终结点具有不同的请求处理方式。ASP.NET Core应用可以利用RequestDelegate对象来表示HTTP请求处理器,每个终结点都封装了一个RequestDelegate对象并用它来处理路由给它的请求。如下图所示,除了请求处理器,终结点还提供了一个用来存放元数据的容器,路由过程中的很多行为都可以通过相应的元数据来控制。

一、Endpoint & EndpointBuilder

路由系统中的终结点通过如下所示的Endpoint类型表示。组成终结点的两个核心成员(请求处理器和元数据集合)分别体现为只读属性RequestDelegate和Metadata。除此之外,终结点还有一个显示名称的只读属性DisplayName。

public class Endpoint
{
public string DisplayName { get; }
public RequestDelegate RequestDelegate { get; }
public EndpointMetadataCollection Metadata { get; } public Endpoint(RequestDelegate requestDelegate, EndpointMetadataCollection metadata, string displayName);
}

终结点元数据集合体现为一个EndpointMetadataCollection对象。由于终结点并未对元数据的形式做任何限制,原则上任何对象都可以作为终结点的元数据,所以EndpointMetadataCollection对象本质上就是一个元素类型为Object的集合。如下面的代码片段所示,EndpointMetadata
Collection对象是一个只读列表,它包含的元数据需要在该集合被创建时被提供。

public sealed class EndpointMetadataCollection : IReadOnlyList<object>
{
public object this[int index] { get; }
public int Count { get; } public EndpointMetadataCollection(IEnumerable<object> items);
public EndpointMetadataCollection(params object[] items); public Enumerator GetEnumerator();
public T GetMetadata<T>() where T: class;
public IReadOnlyList<T> GetOrderedMetadata<T>() where T: class; IEnumerator<object> IEnumerable<object>.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator();
}

我们可以调用泛型方法GetMetadata<T>得到指定类型的元数据,由于多个具有相同类型的元数据可能会被添加到集合中,所以这个方法会采用“后来居上”的策略,返回最后被添加的元数据对象。如果没有指定类型的元数据,该方法会返回指定类型的默认值。如果希望按序返回指定类型的所有元数据,可以调用另一个泛型方法GetOrderedMetadata<T>。

路由系统利用EndpointBuilder来构建表示终结点的Endpoint对象。如下面的代码片段所示,EndpointBuilder是一个抽象类,针对终结点的构建体现在抽象的Build方法中。EndpointBuilder定义了对应的属性来设置终结点的请求处理器、元数据和显示名称。

public abstract class EndpointBuilder
{
public RequestDelegate RequestDelegate { get; set; }
public string DisplayName { get; set; }
public IList<object> Metadata { get; } public abstract Endpoint Build();
}

二、RouteEndpoint & RouteEndpointBuilder

路由系统的终结点体现为一个RouteEndpoint对象,它实际上是将映射的路由模式融入终结点中。如下面的代码片段所示,派生于Endpoint的RouteEndpoint类型有一个名为RoutePattern的只读属性,返回的正是表示路由模式的RoutePattern对象。除此之外,RouteEndpoint类型还有另一个表示注册顺序的Order属性。

public sealed class RouteEndpoint : Endpoint
{
public RoutePattern RoutePattern { get; }
public int Order { get; } public RouteEndpoint(RequestDelegate requestDelegate, RoutePattern routePattern, int order, EndpointMetadataCollection metadata, string displayName);
}

RouteEndpoint对象由RouteEndpointBuilder构建而成。如下面的代码片段所示,RouteEndpoint
Builder类型派生于抽象基类EndpointBuilder。在重写的Build方法中,RouteEndpointBuilder类型根据构造函数或者属性指定的信息创建出返回的RouteEndpoint对象。

public sealed class RouteEndpointBuilder : EndpointBuilder
{
public RoutePattern RoutePattern { get; set; }
public int Order { get; set; } public RouteEndpointBuilder(RequestDelegate requestDelegate, RoutePattern routePattern, int order)
{
base.RequestDelegate = requestDelegate;
RoutePattern = routePattern;
Order = order;
} public override Endpoint Build() => new RouteEndpoint(base.RequestDelegate, RoutePattern, Order,
new EndpointMetadataCollection((IEnumerable<object>)base.Metadata),
base.DisplayName);
}

三、EndpointDataSource

路由系统中的终结点体现了针对某类请求的处理方式,它们的来源具有不同的表现形式,终结点的数据源通过EndpointDataSource表示。如下图所示,一个EndpointDataSource对象可以提供多个表示终结点的Endpoint对象,为应用提供相应的EndpointDataSource对象是路由注册的一项核心工作。

如下面的代码片段所示,EndpointDataSource是一个抽象类,除了表示提供终结点列表的只读属性Endpoints,它还提供了一个GetChangeToken方法,我们可以利用这个方法返回的IChangeToken对象来感知数据源的变化。

public abstract class EndpointDataSource
{
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
public abstract IChangeToken GetChangeToken();
}

路由系统提供了一个DefaultEndpointDataSource类型。如下面的代码片段所示,Default
EndpointDataSource通过重写的Endpoints属性提供的终结点列表在构造函数中是显式指定的,其GetChangeToken方法返回的是一个不具有感知能力的NullChangeToken对象。

public sealed class DefaultEndpointDataSource : EndpointDataSource
{
private readonly IReadOnlyList<Endpoint> _endpoints;
public override IReadOnlyList<Endpoint> Endpoints => _endpoints; public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints) =>_endpoints = (IReadOnlyList<Endpoint>) new List<Endpoint>(endpoints); public DefaultEndpointDataSource(params Endpoint[] endpoints) =>_endpoints = (Endpoint[]) endpoints.Clone(); public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
}

对于本章开篇演示的一系列路由实例来说,我们最终注册的实际上是一个类型为ModelEndpointDataSource的终结点数据源,它依然是一个未被公开的内部类型。要理解ModelEndpointDataSource针对终结点的提供机制,就必须了解另一个名为 IEndpointConventionBuilder的接口。顾名思义,IEndpointConventionBuilder体现了一种针对“约定”的终结点构建方式。

如下面的代码片段所示,该接口定义了一个唯一的Add方法,针对终结点构建的约定体现在该方法类型为Action<EndpointBuilder>的参数上。IEndpointConventionBuilder接口还有如下所示的3个扩展方法,用来为构建的终结点设置显示名称和元数据。

public interface IEndpointConventionBuilder
{
void Add(Action<EndpointBuilder> convention);
} public static class RoutingEndpointConventionBuilderExtensions
{
public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, Func<EndpointBuilder, string> func) where TBuilder : IEndpointConventionBuilder
{
builder.Add(it=>it.DisplayName = func(it));
return builder;
} public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, string displayName) where TBuilder : IEndpointConventionBuilder
{
builder.Add(it => it.DisplayName = displayName);
return builder;
}
public static TBuilder WithMetadata<TBuilder>(this TBuilder builder, params object[] items) where TBuilder : IEndpointConventionBuilder
{ builder.Add(it => Array.ForEach(items, item => it.Metadata.Add(item)));
return builder;
}
}

ModelEndpointDataSource这个终结点数据源内部会使用一个名为DefaultEndpointConventionBuilder的类型,如下所示的代码片段给出了这两个类型的完整实现。从给出的代码片段可以看出,ModelEndpointDataSource的GetChangeToken方法返回的依然是一个不具有感知能力的NullChangeToken对象。

internal class DefaultEndpointConventionBuilder : IEndpointConventionBuilder
{
private readonly List<Action<EndpointBuilder>> _conventions;
internal EndpointBuilder EndpointBuilder { get; } public DefaultEndpointConventionBuilder(EndpointBuilder endpointBuilder)
{
EndpointBuilder = endpointBuilder;
_conventions = new List<Action<EndpointBuilder>>();
} public void Add(Action<EndpointBuilder> convention) =>_conventions.Add(convention); public Endpoint Build()
{
foreach (var convention in _conventions)
{
convention(EndpointBuilder);
}
return EndpointBuilder.Build();
}
} internal class ModelEndpointDataSource : EndpointDataSource
{
private List<DefaultEndpointConventionBuilder> _endpointConventionBuilders; public ModelEndpointDataSource() => _endpointConventionBuilders = new List<DefaultEndpointConventionBuilder>(); public IEndpointConventionBuilder AddEndpointBuilder(EndpointBuilder endpointBuilder)
{
var builder = new DefaultEndpointConventionBuilder(endpointBuilder);
_endpointConventionBuilders.Add(builder);
return builder;
} public override IChangeToken GetChangeToken()=> NullChangeToken.Singleton;
public override IReadOnlyList<Endpoint> Endpoints => _endpointConventionBuilders.Select(it => it.Build()).ToArray();
}

综上所示,ModelEndpointDataSource最终采用下图所示的方式来提供终结点。当我们调用其AddEndpointBuilder方法为它添加一个EndpointBuilder对象时,它会利用这个EndpointBuilder对象创建一个DefaultEndpointConventionBuilder对象。DefaultEndpointConventionBuilder针对终结点的构建最终还是落在EndpointBuilder对象上。

除了上述ModelEndpointDataSource/DefaultEndpointConventionBuilder类型,ASP.NET Core MVC和Razor Pages框架分别根据自身的路由约定提供了针对EndpointDataSource和IEndpointConventionBuilder的实现。路由系统还提供了如下所示的CompositeEndpointDataSource类型。顾名思义,一个CompositeEndpointDataSource对象实际上是对一组EndpointDataSource对象的组合,它重写的Endpoints属性返回的终结点由作为组成成员的EndpointDataSource对象共同提供。它的GetChangeToken方法返回的IChangeToken对象可以帮助我们感知其中任何一个EndpointDataSource对象的改变。

public sealed class CompositeEndpointDataSource : EndpointDataSource
{
public IEnumerable<EndpointDataSource> DataSources { get; }
public override IReadOnlyList<Endpoint> Endpoints { get; } public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources);
public override IChangeToken GetChangeToken();
}

四、IEndpointRouteBuilder

表示终结点数据源的EndpointDataSource对象是借助IEndpointRouteBuilder对象注册的。我们可以在一个IEndpointRouteBuilder对象上注册多个EndpointDataSource对象,它们会被添加到DataSources属性表示的集合中。IEndpointRouteBuilder接口还通过只读属性ServiceProvider提供了作为依赖注入容器的IServiceProvider对象。

public interface IEndpointRouteBuilder
{
ICollection<EndpointDataSource> DataSources { get; }
IServiceProvider ServiceProvider { get; } IApplicationBuilder CreateApplicationBuilder();
}

IEndpointRouteBuilder接口的CreateApplicationBuilder方法会帮助我们创建一个新的IApplicationBuilder对象。如果某个终结点针对请求处理的逻辑相对复杂,需要多个终结点协同完成,就可以将这些中间件注册到这个IApplicationBuilder对象上,然后利用它创建的Request
Delegate对象来处理路由的请求。如下所示的内部类型DefaultEndpointRouteBuilder是对IEndpointRouteBuilder接口的默认实现。

internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
{
public ICollection<EndpointDataSource> DataSources { get; }
public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
public IApplicationBuilder ApplicationBuilder { get; } public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
{
ApplicationBuilder = applicationBuilder;
DataSources = new List<EndpointDataSource>();
} public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
}

本节的内容以终结点为核心,表示终结点的Endpoint对象来源于通过EndpointDataSource对象表示的数据源,EndpointDataSource对象注册到IEndpointRouteBuilder对象上。以IEndpointRouteBuilder、EndpointDataSource和Endpoint为核心的终结点模型体现在下图中。

ASP.NET Core路由中间件[1]: 终结点与URL的映射
ASP.NET Core路由中间件[2]: 路由模式
ASP.NET Core路由中间件[3]: 终结点
ASP.NET Core路由中间件[4]: EndpointRoutingMiddleware和EndpointMiddleware
ASP.NET Core路由中间件[5]: 路由约束

ASP.NET Core路由中间件[3]: 终结点(Endpoint)的更多相关文章

  1. ASP.NET Core路由中间件[1]: 终结点与URL的映射

    目录 一.路由注册 二.设置内联约束 三.默认路由参数 四.特殊的路由参数 借助路由系统提供的请求URL模式与对应终结点(Endpoint)之间的映射关系,我们可以将具有相同URL模式的请求分发给应用 ...

  2. ASP.NET Core路由中间件[2]: 路由模式

    一个Web应用本质上体现为一组终结点的集合.终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系.借助这个映射关系,客户端可 ...

  3. asp.net core mvc 中间件之路由

    asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...

  4. ASP.NET Core:中间件

    一.什么是中间件 我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中.而中间件就是用于组成应用程序管道来处理请求和响应的 ...

  5. ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 路由 前两章节中,我们提到 ASP.NET Core 支持 MVC 开发 ...

  6. 如何传递参数给ASP.NET Core的中间件(Middleware)

    问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...

  7. asp.net core mvc 中间件之WebpackDevMiddleware

    asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...

  8. 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?

    原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...

  9. 构建可读性更高的 ASP.NET Core 路由

    原文:构建可读性更高的 ASP.NET Core 路由 一.前言 不知你在平时上网时有没有注意到,绝大多数网站的 URL 地址都是小写的英文字母,而我们使用 .NET/.NET Core MVC 开发 ...

随机推荐

  1. 第4.8节 三目运算、del和pass语句

    一.三目运算 Python的三目运算与C语言的三目运算非常类似,具体语法如下: 条件为真的赋值表达式   if  条件   else 条件为假的表达式 三目运算实际上就是一种表达式计算,当对应if后面 ...

  2. 在Python中使用moviepy进行音视频剪辑混音合成时输出文件无声音问题

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在使用moviepy进行音视频剪辑时发现输出成功但 ...

  3. PyQt(Python+Qt)学习随笔:QLineEdit行编辑器功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 QLineEdit部件是一个单行文本编辑器,支持撤消和重做. ...

  4. PyQt(Python+Qt)学习随笔:Qt Designer中部件的accessibleDescription和accessibleName辅助阅读属性

    accessibleDescription和accessibleName属性都是用于残疾人辅助阅读的,这两个属性都有国际化属性(关于国际化请参考<PyQt(Python+Qt)学习随笔:Qt D ...

  5. mysql中innodb引擎的mvcc机制和BufferPool缓存机制

    一.MVCC (1)mvcc主要undo日志版本链和read-view一致性视图来保证多事务的并发控制,mvcc是innodb的一种特殊机制,他保证了事务四大特性之一的隔离性(原子性,一致性,隔离性) ...

  6. 使用 typescript 快速开发一个 cli

    cli 的全称 command-line interface(命令行界面),也就是前端同学常用的脚手架,比如 yo.vue cli.react cli 等. cli 可以方便我们快速创建项目,下图是引 ...

  7. 老男孩培训作业day1

    作业一:博客(开通博客园) 作业二:编写登录接口 输入用户名密码 认证成功后显示欢迎信息 输错三次后锁定 作业三:多级菜单 三级菜单 可依次选择进入各子菜单 所需新知识点:列表.字典(列表和字典的相互 ...

  8. POJ3565

    题目大意: 给定\(n\)个蚂蚁和\(n\)颗苹果树的坐标,要求每个蚂蚁爬到一颗苹果树旁,使得每个蚂蚁路线不相交且路线总长度最小,求每个蚂蚁爬到哪个苹果树旁? 首先假设有两只蚂蚁路径相交,那么这两个蚂 ...

  9. 容器编排系统之ReplicaSet和Deployment控制器

    前文我们了解了k8s上的Pod资源的生命周期.健康状态和就绪状态探测以及资源限制相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/14143610.htm ...

  10. 某毒霸网站存在信息泄露和sql注入

    通过钟馗之眼搜索目标网站的证书使用者找到信息泄露的网站, 点击上图中的edit会跳转 手工注入sql发现有效 that's all