热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ASP.NETCore路由中间件[3]:终结点(Endpoint)

本章介绍的是最早发布于ASP.NETCore2.2中的新路由系统,由于它采用基于终结点映射的策略,所以我们将其称为终结点路由。终结点路由自然以终结点为核心,所以先介绍终结点在路由系
本章介绍的是最早发布于ASP.NET Core 2.2中的新路由系统,由于它采用基于终结点映射的策略,所以我们将其称为终结点路由。终结点路由自然以终结点为核心,所以先介绍终结点在路由系统中的表现形式。

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

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

15-9

一、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() where T: class;    
    public IReadOnlyList GetOrderedMetadata() where T: class;
   
    IEnumerator<object> IEnumerable<object>.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

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

路由系统利用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对象是路由注册的一项核心工作。

15-10

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

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

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

public sealed class DefaultEndpointDataSource : EndpointDataSource
{
    private readonly IReadOnlyList _endpoints;
    public override IReadOnlyList Endpoints => _endpoints;

    public DefaultEndpointDataSource(IEnumerable endpoints) =>_endpoints = (IReadOnlyList) new List(endpoints);

    public DefaultEndpointDataSource(params Endpoint[] endpoints) =>_endpoints = (Endpoint[]) endpoints.Clone();

    public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;    
}

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

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

public interface IEndpointConventionBuilder
{
    void Add(Action convention);
}

public static class RoutingEndpointConventionBuilderExtensions
{
    public static TBuilder WithDisplayName(this TBuilder builder, Funcstring> func) where TBuilder : IEndpointConventionBuilder
    {      
        builder.Add(it=>it.DisplayName = func(it));
        return builder;
    }

    public static TBuilder WithDisplayName(this TBuilder builder, string displayName) where TBuilder : IEndpointConventionBuilder
    {
        builder.Add(it => it.DisplayName = displayName);
        return builder;
    }
    public static TBuilder WithMetadata(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> _conventions;
    internal EndpointBuilder EndpointBuilder { get; }

    public DefaultEndpointConventionBuilder(EndpointBuilder endpointBuilder)
    {
        EndpointBuilder = endpointBuilder;
        _conventions = new List>();
    }

    public void Add(Action convention) =>_conventions.Add(convention);

    public Endpoint Build()
    {
        foreach (var convention in _conventions)
        {
            convention(EndpointBuilder);
        }
        return EndpointBuilder.Build();
    }
}

internal class ModelEndpointDataSource : EndpointDataSource
{
    private List _endpointConventionBuilders;

    public ModelEndpointDataSource() => _endpointCOnventionBuilders= new List();

    public IEndpointConventionBuilder AddEndpointBuilder(EndpointBuilder endpointBuilder)
    {
        var builder = new DefaultEndpointConventionBuilder(endpointBuilder);
        _endpointConventionBuilders.Add(builder);
        return builder;
    }

    public override IChangeToken GetChangeToken()=> NullChangeToken.Singleton;
    public override IReadOnlyList Endpoints  => _endpointConventionBuilders.Select(it => it.Build()).ToArray();
}

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

15-20

除了上述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 DataSources { get; }
    public override IReadOnlyList Endpoints { get; }

    public CompositeEndpointDataSource(IEnumerable endpointDataSources);
    public override IChangeToken GetChangeToken();
}

四、IEndpointRouteBuilder

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

public interface IEndpointRouteBuilder
{
    ICollection DataSources { get; }
    IServiceProvider ServiceProvider { get; }

    IApplicationBuilder CreateApplicationBuilder();
}

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

internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
{
    public ICollection DataSources { get; }
    public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
    public IApplicationBuilder ApplicationBuilder { get; }

    public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
    {
        ApplicationBuilder = applicationBuilder;
        DataSources = new List();
    }

    public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
}

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

15-12

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


推荐阅读
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • Flutter 核心技术与混合开发模式深入解析
    本文深入探讨了 Flutter 的核心技术,特别是其混合开发模式,包括统一管理模式和三端分离模式,以及混合栈原理。通过对比不同模式的优缺点,帮助开发者选择最适合项目的混合开发策略。 ... [详细]
  • 深入理解Java多线程与并发机制
    本文探讨了Java多线程和并发机制的核心概念,包括多线程类的分类、执行器框架、并发容器及控制工具。通过详细解析这些组件,帮助开发者更好地理解和应用多线程技术。 ... [详细]
  • 使用Tkinter构建51Ape无损音乐爬虫UI
    本文介绍了如何使用Python的内置模块Tkinter来构建一个简单的用户界面,用于爬取51Ape网站上的无损音乐百度云链接。虽然Tkinter入门相对简单,但在实际开发过程中由于文档不足可能会带来一些不便。 ... [详细]
  • 在 Kubernetes 中,Pod 的调度通常由集群的自动调度策略决定,这些策略主要关注资源充足性和负载均衡。然而,在某些场景下,用户可能需要更精细地控制 Pod 的调度行为,例如将特定的服务(如 GitLab)部署到特定节点上,以提高性能或满足特定需求。本文深入解析了 Kubernetes 的亲和性调度机制,并探讨了多种优化策略,帮助用户实现更高效、更灵活的资源管理。 ... [详细]
  • 在尝试加载支持推送通知的iOS应用程序的Ad Hoc构建时,遇到了‘no valid aps-environment entitlement found for application’的错误提示。本文将探讨此错误的原因及多种可能的解决方案。 ... [详细]
  • 本文详细介绍如何在华为鲲鹏平台上构建和使用适配ARM架构的Redis Docker镜像,解决常见错误并提供优化建议。 ... [详细]
  • 尽管Medium是一个优秀的发布平台,但在其之外拥有自己的博客仍然非常重要。这不仅提供了另一个与读者互动的渠道,还能确保您的内容安全。本文将介绍如何使用Bash脚本将Medium文章迁移到个人博客。 ... [详细]
  • DirectShow Filter 开发指南
    本文总结了 DirectShow Filter 的开发经验,重点介绍了 Source Filter、In-Place Transform Filter 和 Render Filter 的实现方法。通过使用 DirectShow 提供的类,可以简化 Filter 的开发过程。 ... [详细]
  • Spring 中策略模式的应用:Resource 接口详解
    本文探讨了在 Spring 框架中如何利用 Resource 接口实现资源访问策略。Resource 接口作为资源访问策略的抽象,通过多种实现类支持不同类型的资源访问。 ... [详细]
  • vue引入echarts地图的四种方式
    一、vue中引入echart1、安装echarts:npminstallecharts--save2、在main.js文件中引入echarts实例:  Vue.prototype.$echartsecharts3、在需要用到echart图形的vue文件中引入:   importechartsfrom&amp;quot;echarts&amp;quot;;4、如果用到map(地图),还 ... [详细]
  • 本文介绍了 Python 中的基本数据类型,包括不可变数据类型(数字、字符串、元组)和可变数据类型(列表、字典、集合),并详细解释了每种数据类型的使用方法和常见操作。 ... [详细]
  • 本文详细介绍了 Pentaho Kettle 中 RowMetaInterface.writeMeta 方法的使用,并提供了多个代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
author-avatar
-sunnydays
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有