.NET 8 IEndpointRouteBuilder详解

Map

​ 经过对 WebApplication 的初步剖析,我们已经大致对Web应用的骨架有了一定了解,现在我们来看一下Hello World案例中仅剩的一条代码:

c# 复制代码
app.MapGet("/", () => "Hello World!"); // 3 添加路由处理

​ 老规矩,看签名:

c# 复制代码
public static RouteHandlerBuilder MapGet(this IEndpointRouteBuilder endpoints,
      [StringSyntax("Route")] string pattern,Delegate handler){
    return endpoints.MapMethods(pattern, (IEnumerable<string>) EndpointRouteBuilderExtensions.GetVerb, handler);
}

​ 我们已经解释过 IEndpointRouteBuilder 的定义了,即为程序定义路由构建的约定 。这次看到的是他的拓展方法 Map ,该方法是诸如 MapGet、MapPost、MapPut、MapDelete、MapPatch、MapMethods 的底层方法:

​ 他的实现就是为了给IEndpointRouteBuilder 的 DataSources 添加一个 RouteEndpointDataSource

c# 复制代码
private static RouteHandlerBuilder Map(this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate handler, IEnumerable<string> httpMethods, bool isFallback)
{
	return endpoints.GetOrAddRouteEndpointDataSource().AddRouteHandler(pattern, handler, httpMethods, isFallback, RequestDelegateFactory.InferMetadata, RequestDelegateFactory.Create);
}

​ RouteEndpointDataSource 继承自 EndpointDataSource,提供一组RouteEndpoint

c# 复制代码
public override IReadOnlyList<RouteEndpoint> Endpoints
{
	get
	{
		RouteEndpoint[] array = new RouteEndpoint[_routeEntries.Count];
		for (int i = 0; i < _routeEntries.Count; i++)
		{
			array[i] = (RouteEndpoint)CreateRouteEndpointBuilder(_routeEntries[i]).Build();
		}
		return array;
	}
}

Endpoint

​ RouteEndpoint 继承自 Endpoint。多了一个 RoutePattern 属性,用于路由匹配,支持模式路由。还有一个 Order 属性,用于处理多匹配源下的优先级问题。

c# 复制代码
public sealed class RouteEndpoint : Endpoint
{
	public int Order { get; }
	public RoutePattern RoutePattern { get; }
}

​ Endpoint 是一个应用程序中路的一个逻辑终结点。

c# 复制代码
public string? DisplayName { get; } // 终结点名称
public EndpointMetadataCollection Metadata { get; }  // 元数据
public RequestDelegate? RequestDelegate { get; }  // 请求委托

​ 其中 Metadata 就是一个object集合,包含描述、标签、权限等数据,这一般是框架用到的,后面会再次见到它。重点是 RequestDelegate:HttpContext 首次登场,相信有过Web开发经验的同学熟悉的不能再熟悉。该委托其实就是一个Func<HttpContext, Task>,用于处理HTTP请求,由于TAP的普及,所以返回的Task。

c# 复制代码
public delegate Task RequestDelegate(HttpContext context);

​ Endpoint 一般由 EndpointBuilder 构建,他能够额外组装Filter

c# 复制代码
public IList<Func<EndpointFilterFactoryContext, EndpointFilterDelegate, EndpointFilterDelegate>> FilterFactories

EndpointDataSource

​ 经过对 MapGet 的剖析我们最终发现,所有的终结点都被挂载在了 EndpointDataSource

c# 复制代码
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
public virtual IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context)

​ 除了被大家熟悉的 Endpoints 还提供了一个方法 GetGroupedEndpoints:在给定指定前缀和约定的情况下,获取此EndpointDataSource 的所有 Endpoint的。

c# 复制代码
public virtual IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context)
{
	IReadOnlyList<Endpoint> endpoints = Endpoints;
	RouteEndpoint[] array = new RouteEndpoint[endpoints.Count];
	for (int i = 0; i < endpoints.Count; i++)
	{
		Endpoint endpoint = endpoints[i];
		if (!(endpoint is RouteEndpoint routeEndpoint))
		{
			throw new NotSupportedException(Resources.FormatMapGroup_CustomEndpointUnsupported(endpoint.GetType()));
		}
		RoutePattern routePattern = RoutePatternFactory.Combine(context.Prefix, routeEndpoint.RoutePattern);
		RouteEndpointBuilder routeEndpointBuilder = new RouteEndpointBuilder(routeEndpoint.RequestDelegate, routePattern, routeEndpoint.Order)
		{
			DisplayName = routeEndpoint.DisplayName,
			ApplicationServices = context.ApplicationServices
		};
		foreach (Action<EndpointBuilder> convention in context.Conventions)
		{
			convention(routeEndpointBuilder);
		}
		foreach (object metadatum in routeEndpoint.Metadata)
		{
			routeEndpointBuilder.Metadata.Add(metadatum);
		}
		foreach (Action<EndpointBuilder> finallyConvention in context.FinallyConventions)
		{
			finallyConvention(routeEndpointBuilder);
		}
		array[i] = (RouteEndpoint)routeEndpointBuilder.Build();
	}
	return array;
}

​ 通过剖析 RouteGroupContext,很容易发觉,Prefix 是一个路由前缀,Conventions 和 FinallyConventions 是两个约定hook。它专为 RouteEndpoint 独有,通过 GetGroupedEndpoints 方法,组的前缀和约定,会作用到每一个路由终结点。

c# 复制代码
public sealed class RouteGroupContext
{
    public required RoutePattern Prefix { get; init; }
    public IReadOnlyList<Action<EndpointBuilder>> Conventions { get; init; } = Array.Empty<Action<EndpointBuilder>>();
    public IReadOnlyList<Action<EndpointBuilder>> FinallyConventions { get; init; } = Array.Empty<Action<EndpointBuilder>>();
}
相关推荐
gudufy13 小时前
EasyAdminBlazor 生产部署完整指南:IIS、Windows 服务与宝塔
iis·asp.net core·在线升级·windows服务·easyadminblazor·blazor部署·宝塔部署
金士顿18 天前
ASP.NET Core Native AOT + systemd 实战:把 Linux ARM64 程序变成可靠的设备服务
linux·嵌入式·asp.net core·arm64
金士顿18 天前
System.Text.Json Source Generator 深度解析:为什么 Native AOT 不喜欢反射
linux·asp.net core·arm64·net·native aot
金士顿18 天前
ASP.NET Core Native AOT 到底是什么?从 C#、IL、JIT 一直讲到 Linux ARM64 ELF
嵌入式·asp.net core
金士顿22 天前
ASP.NET Core BackgroundService 深度解析:设备后台任务到底应该怎么设计
linux·嵌入式·asp.net core·arm64
ChaITSimpleLove4 个月前
深入解析 ASP.NET Core Options 模式:IOptions、IOptionsSnapshot 与 IOptionsMonitor 实战指南
asp.net core·snapshot·依赖注入di·options模式·ioptions·ioptionsmonitor·配置热更新
医疗信息化王工4 个月前
医院自律端系统——预警处置模块全栈实战(ASP.NET Core + Vue3 + Quartz 定时调度)
mysql·postgresql·vue·asp.net core·quartz
Esofar5 个月前
Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施
c#·ddd·asp.net core·cqrs·dddify·clean architecture
CSharp精选营5 个月前
.NET 8 Web开发入门(三):解构引擎——依赖注入(DI)与中间件管道
中间件·asp.net core·依赖注入·ioc容器·请求管道·服务生命周期
医疗信息化王工6 个月前
基于ASP.NET Core的医院输血审核系统设计与实现
后端·mvc·asp.net core·输血审核