ASP.NET Core路由中间件[3]: 终结点(Endpoint)
到目前为止,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)的更多相关文章
- ASP.NET Core路由中间件[1]: 终结点与URL的映射
目录 一.路由注册 二.设置内联约束 三.默认路由参数 四.特殊的路由参数 借助路由系统提供的请求URL模式与对应终结点(Endpoint)之间的映射关系,我们可以将具有相同URL模式的请求分发给应用 ...
- ASP.NET Core路由中间件[2]: 路由模式
一个Web应用本质上体现为一组终结点的集合.终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系.借助这个映射关系,客户端可 ...
- asp.net core mvc 中间件之路由
asp.net core mvc 中间件之路由 路由中间件 首先看路由中间件的源码 先用httpContext实例化一个路由上下文,然后把中间件接收到的路由添加到路由上下文的路由集合 然后把路由上下文 ...
- ASP.NET Core:中间件
一.什么是中间件 我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中.而中间件就是用于组成应用程序管道来处理请求和响应的 ...
- ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 路由 前两章节中,我们提到 ASP.NET Core 支持 MVC 开发 ...
- 如何传递参数给ASP.NET Core的中间件(Middleware)
问题描述 当我们在ASP.NET Core中定义和使用中间件(Middleware)的时候,有什么好的办法可以给中间件传参数吗? 解决方案 在ASP.NET Core项目中添加一个POCO类来传递参数 ...
- asp.net core mvc 中间件之WebpackDevMiddleware
asp.net core mvc 中间件之WebpackDevMiddleware WebpackDevMiddleware中间件主要用于开发SPA应用,启用Webpack,增强网页开发体验.好吧,你 ...
- 如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容?
原文:如何在ASP.NET Core自定义中间件中读取Request.Body和Response.Body的内容? 文章名称: 如何在ASP.NET Core自定义中间件读取Request.Body和 ...
- 构建可读性更高的 ASP.NET Core 路由
原文:构建可读性更高的 ASP.NET Core 路由 一.前言 不知你在平时上网时有没有注意到,绝大多数网站的 URL 地址都是小写的英文字母,而我们使用 .NET/.NET Core MVC 开发 ...
随机推荐
- moviepy音视频剪辑:使用fl_time报错OSError: MoviePy error: failed to read the first frame of video file
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 在m ...
- PyQt(Python+Qt)学习随笔:工具箱(QToolBox)编程使用的步骤及示例代码
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 使用toolBox开发应用时,通过Designer设计ui界面时,只能在Designer中设计too ...
- Python基础篇学习感悟:学如不及,犹恐失之
从2019年3月底开始学习Python,4月12日在CSDN发表第一篇博文,时至今日已有4个月零12天. 4个多月的学习,老猿从一个Python小白成长到今天,可以说对Python这门语言已经略知一二 ...
- PyQt(Python+Qt)学习随笔:视图中类QAbstractItemView的dragDropOverwriteMode属性不能覆盖写的问题
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在<PyQt(Python+Qt)学习随笔:视图中类QAbstractItemView的dra ...
- PHP代码审计分段讲解(14)
30题利用提交数组绕过逻辑 本篇博客是PHP代码审计分段讲解系列题解的最后一篇,对于我这个懒癌患者来说,很多事情知易行难,坚持下去,继续学习和提高自己. 源码如下: <?php $role = ...
- 我摊牌了,大厂面试Linux就这5个问题
说真的,这就是<我想进大厂>系列第八篇,但是Linux的问题确实很少,就这样,强行编几个没有营养的问题也没啥意义. 1.CPU负载和CPU利用率的区别是什么? 首先,我们可以通过uptim ...
- 第二篇 Scrum 冲刺博客
一.站立式会议 1. 会议照片 2. 工作汇报 成员名称 昨日(23日)完成的工作 今天(24日)计划完成的工作 工作中遇到的困难 陈锐基 - 完成个人资料编辑功能- 对接获取表白动态的接口数据并渲染 ...
- 百度前端技术学院-基础-day3
2020.9.15 第三天的课程内容,复习了一些CSS的基础属性(果然不用就会忘). 学习了一些新的属性,比如text-shadow.text-indent(还不是很清楚).font-style等等. ...
- 一份平民化的MySQL性能优化指南
前言 近期在重新学习总结MySQL数据库性能优化的相关知识,本文是根据自己学习以及日常性能测试调优过程中总结的经验整理了一份平民化的优化指南,希望对大家在进行MySQL调优分析时有帮助! MySQ ...
- 实战演习:mysqlbinlog恢复bin-log数据
mysqlbinlog恢复bin-log数据 Binlog日志即binary log,是二进制日志文件,有两个作用,一个是增量备份,另一个是主从复制,即从节点同步主节点数据时获取的即是bin-log, ...