路由负责匹配传入的 HTTP 请求,然后将这些请求发送到应用的可执行终结点。 终结点是应用的可执行请求处理代码单元。 终结点在应用中进行定义,并在应用启动时进行配置。 终结点匹配过程可以从请求的 URL 中提取值,并为请求处理提供这些值。 通过使用应用中的终结点信息,路由还能生成映射到终结点的 URL。

  应用可以使用以下内容配置路由:

  • Controller
  • Razor Pages
  • SignalR
  • gRPC
  • 启用终结点的中间件,例如运行状况检查。
  • 通过路由注册的委托和 Lambda。

路由基本知识

以下代码演示路由的基本示例:

1
2
3
4
5
6
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

  前面的示例包含使用 MapGet 方法的单个终结点:

  • 当 HTTP GET 请求发送到根 URL / 时:
    • 将执行请求委托。
    • Hello World! 会写入 HTTP 响应。
  • 如果请求方法不是 GET 或根 URL 不是 /,则无路由匹配,并返回 HTTP 404。

  路由使用一对由 UseRoutingUseEndpoints 注册的中间件:

  • UseRouting :向中间件管道添加路由匹配。 此中间件会查看应用中定义的终结点集,并根据请求选择最佳匹配。
  • UseEndpoints : 向中间件管道添加终结点执行。 它会运行与所选终结点关联的委托。

  应用通常不需要调用 UseRoutingUseEndpointsWebApplicationBuilder 配置中间件管道,该管道使用 UseRoutingUseEndpoints 包装在 Program.cs 中添加的中间件。 但是,应用可以通过显式调用这些方法来更改 UseRoutingUseEndpoints 的运行顺序。 例如,下面的代码显式调用 UseRouting

1
2
3
4
5
6
7
8
9
app.Use(async (context, next) =>
{
// ...
await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

在上述代码中:

  • app.Use 的调用会注册一个在管道的开头运行的自定义中间件。
  • UseRouting 的调用将路由匹配中间件配置为在自定义中间件之后运行。
  • 使用 MapGet 注册的终结点在管道末尾运行。

  如果前面的示例不包含对 UseRouting 的调用,则自定义中间件将在路由匹配中间件之后运行。

终结点

  MapGet方法用于定义终结点。终结点可以:

  • 通过匹配URL和HTTP方法来选择
  • 通过运行委托来运行

  可通过应用匹配和执行的终结点在 UseEndpoints 中进行配置。 例如,MapGetMapPostMapGet将请求委托连接到路由系统。 其他方法可用于将 ASP.NET Core 框架功能连接到路由系统:

  • 用于 Razor PagesMapRazorPages
  • 用于 控制器MapControllers
  • 用于 SignalRMapHub<THub>
  • 用于 gRPCMapGrpcService<TService>

路由概念

  路由系统通过添加功能强大的终结点概念,构建在中间件管道之上。 终结点代表应用的功能单元,在路由、授权和任意数量的 ASP.NET Core 系统方面彼此不同。

终结点定义

  ASP.NET Core 终结点是:

  • 可执行:具有 RequestDelegate
  • 可扩展:具有元数据(Endpoint.Metadata)集合
  • Selectable:可选择性包含路由信息(RouteEndpoint.RoutePattern)。
  • 可枚举:可通过从 EndpointDataSource 中检索 EndpointDataSource 来列出终结点集合。

  以下代码显示了如何检索和检查与当前请求匹配的终结点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
app.Use(async (context, next) =>
{
var currentEndpoint = context.GetEndpoint();

if (currentEndpoint is null)
{
await next(context);
return;
}

Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

// 判断是否为路由终结点
if (currentEndpoint is RouteEndpoint routeEndpoint)
{
Console.WriteLine($" - Route Pattern: {routeEndpoint.RoutePattern}");
}

// 输出终结点元数据
foreach (var endpointMetadata in currentEndpoint.Metadata)
{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}

await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

  如果选择了终结点,可从 HttpContext 中进行检索。 可以检查其属性。 终结点对象是不可变的,并且在创建后无法修改。 最常见的终结点类型是 RouteEndpointRouteEndpoint 包括允许自己被路由系统选择的信息。

  • 调用 UseRouting 之前,终结点始终为 null
  • 如果找到匹配项,则 UseRoutingUseEndpoints 之间的终结点为非 null
  • 如果找到匹配项,则 UseEndpoints 中间件即为终端。
  • 仅当找不到匹配项时才执行 UseEndpoints 后的中间件。

UseRouting之前使用app.Use中调用context.GetEndpoint()null;在UseRouting之后才不为空

  UseRouting 中间件使用 SetEndpoint 方法将终结点附加到当前上下文。 可以将 UseRouting 中间件替换为自定义逻辑,同时仍可获得使用终结点的益处。 终结点是中间件等低级别基元,不与路由实现耦合。 大多数应用都不需要将 UseRouting 替换为自定义逻辑。

  UseEndpoints 中间件旨在与 UseRouting 中间件配合使用。 执行终结点的核心逻辑并不复杂。 使用 GetEndpoint 检索终结点,然后调用其 RequestDelegate 属性。

  下面的代码演示中间件如何影响或响应路由:

1
public class RequiresAuditAttribute : Attribute { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
}

await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());