我想测试最小API
WebApplication
的路由。给定 HttpRequestMessage
,如何获取路线名称?
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRouting().Configure<RouteOptions>(options => {});
var app = builder.Build();
app.MapGet("/hello", () => "Hello!").WithName(RouteNames.Hello);
app.MapGet("/world", () => "World!").WithName(RouteNames.World);
//app.Run();
var request = new HttpRequestMessage(HttpMethod.Get, "/world");
var routeName = GetRouteName(app, request);
Console.WriteLine($"routeName: {routeName}");
string? GetRouteName(WebApplication app, HttpRequestMessage request)
{
return null; // TODO: Implement
}
static class RouteNames
{
public const string Hello = "Hello";
public const string World = "World";
}
在 https://github.com/dotnet/aspnetcore/issues/48034 提出功能请求。
我接受了克里斯的回答。它让我走上了正轨。我希望能够在不执行实际端点的情况下获取端点名称,但我会将其留到另一天。也许我只是不打电话
next
。使用他的答案,我们可以将端点填充到响应中。可以创建一个测试工具来测试我们所有的命名端点。这是使用 <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.5" />
的工作代码:
using Microsoft.AspNetCore.TestHost;
using System.Diagnostics;
var host = new HostBuilder()
.ConfigureWebHost(webHost => webHost.UseTestServer().Configure(app =>
{
app.UseRouting();
app.Use((context, next) =>
{
if (context.GetEndpoint() is Endpoint endpoint)
{
var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
if (endpointName != null)
{
context.Response.Headers.Add(TagKeys.EndpointName, endpointName);
}
}
return next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello", () => "Hello!").WithName(RouteNames.Hello);
endpoints.MapGet("/world", () => "World!").WithName(RouteNames.World);
});
})
.ConfigureServices(services =>
{
services.AddRouting().Configure<RouteOptions>(routeOptions => { });
}
)
).Build();
host.Start();
var httpClient = host.GetTestClient();
await PrintEndpointName(httpClient, new HttpRequestMessage(HttpMethod.Get, "/"));
await PrintEndpointName(httpClient, new HttpRequestMessage(HttpMethod.Get, "/hello"));
await PrintEndpointName(httpClient, new HttpRequestMessage(HttpMethod.Get, "/world"));
async Task PrintEndpointName(HttpClient httpClient, HttpRequestMessage request)
{
var httpResponse = await httpClient.SendAsync(request);
IEnumerable<string>? headers;
httpResponse.Headers.TryGetValues(TagKeys.EndpointName, out headers);
var endpointName = headers?.FirstOrDefault();
Debug.WriteLine($"{((int)httpResponse.StatusCode)} {endpointName}");
}
static class RouteNames
{
public const string Hello = "Hello";
public const string World = "World";
}
static class TagKeys
{
public const string EndpointName = "endpoint.name";
}
尚不清楚您打算在服务器端还是客户端使用它。它似乎是服务器端。如果您在服务器端,那么您可以在定义时获取映射,而无需任何额外的查找。
匹配时,这是世界上最简单的中间件,它将捕获被调用的端点/路由的名称(如果有):
app.Use( ( context, next ) =>
{
if ( context.GetEndpoint() is Endpoint endpoint )
{
var name = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName ?? "(no name)";
Debug.WriteLine( $"Endpoint = {name}" );
}
return next();
} );
在最小 API 的上下文中,
WithName
将生成 IEndpointNameMetadata.EndpointName
和 IRouteNameMetadata.RouteName
。您可以使用其中一个或添加更多防御来覆盖两者。这种方法也应该适用于基于控制器的 API。
它是指从 URL 中反向查找路由/端点名称,这不受支持,而且说实话,无法真正按预期工作。例如,如果传入请求 URL 为
values/42
,则它永远不会与定义的路由模板 values/{id}
匹配。恒定的路线模板可以工作,但很少见。
我不想阻止你说这是“不可能”,但这真的非常非常困难,可能很脆弱,而且可能不值得付出努力。恕我直言,最好的选项是让路由系统完成其工作,然后从匹配的端点(如果有)中提取您感兴趣的元数据。 这是在看到上面的讨论后,我正在寻找类似的东西,顺便说一句,结果更简单,如下: var endpointName = context.GetEndpoint()?.Metadata.OfType().First().EndpointName;