将末端中间件转换为端点路由

译者: Akini Xu

原文: Converting a terminal middleware to endpoint routing in ASP.NET Core 3.0

作者: Andrew Lock

此文是 升级至 ASP.NET Core 3.0 第4篇:

  1. 转换.NET Standard 2.0类库到.NET Core 3.0
  2. 对比IHostingEnvironment与IHostEnvironment .NET及Core 3.0中的过时类型
  3. 不要在Startup类的构造函数中使用依赖注入
  4. 将末端中间件转换为端点路由
  5. 将集成测试升级至.NET Core 3.0

在这篇文章中,主要介绍端点路由,并演示如何创建一个响应URL请求的端点。 并展示如何将ASP.NET Core 2.x中的末端中间件,升级为ASP.NET Core 3.0中的端点路由。

路由的演变

ASP.NET Core中的路由是将请求URL路径(例如/Orders/1)映射到响应的处理程序的过程。 它主要与MVC中间件一起使用,以将请求映射到ControllersActions。 还可以反向映射,根据指定参数生成URL。

在ASP.NET Core 2.1和更低版本中,通过实现IRouter接口将请求的URL映射到处理程序来处理路由。 通常,不会直接实现这个接口来处理路由,而是在管道中添加MvcMiddleware实现。 一旦请求到达MvcMiddleware,路由会确定传入请求URL应该由哪个ControllerAction来执行。

另外,在执行Action前会经过了各种MVC过滤器。 这些过滤器形成了另一条管道。在一些情况下,我们会重复一些中间件的行为。 一个典型的例子就是CORS政策。 为了对不同的MVC Action配置不同的CORS策略,肯定会有些重复的代码。

中间件管道的“分支”通常用于“伪路由”。在中间件管道中使用Map()方法,当请求的Url前缀满足条件时,执行指定的中间件。

例如,在Startup.cs中的Configure()方法对管道进行分支,当传入路径为/ping时,末端(很多会翻译为终端,我觉得终端有歧义)中间件将执行:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseCors();
app.Map("/ping",
app2 => app2.Run(async context =>
{
await context.Response.WriteAsync("Pong");
});
app.UseMvcWithDefaultRoute();
}

在这种情况下,Run()方法是末端中间件,因为它直接返回响应。这个Map分支对应于应用程序来说,app2没有做其它事,就是一个末端。

MvcMiddleware中的端点(ControllerAction)相比,该末端有点像是二等公民。 从传入路由中解析对象比较麻烦,您必须自己手动实现授权。

另一个问题是,例如,当请求到达UseCors()中间件时,我们需要知道在哪个分支或末端上运行,/ping端点允许跨域请求,而MVC中间件则不允许 。

在ASP.NET Core 2.2中,Microsoft引入了端点路由作为MVC控制器的新路由机制。 它的实现是在MvcMiddleware内部,还无法解决上述问题。 但是在ASP.NET Core 3.0中,它的范围有所扩展,成为了主要的路由机制。

新的端点路由将请求的路由与处理程序的实际执行分开。 这意味着您可以提前知道哪个处理程序将被执行。

如何将之前的ping-pong管道映射到新的端点路由方式?

在ASP.NET Core 2.x中使用Map()时

我们来看一个的自定义中间件,它只返回应用程序的版本信息,它是一个末端中间件,从代码中可以看到它没有再向后调用_netx

public class VersionMiddleware
{
readonly RequestDelegate _next;
static readonly Assembly _entryAssembly = System.Reflection.Assembly.GetEntryAssembly();
static readonly string _version = FileVersionInfo.GetVersionInfo(_entryAssembly.Location).FileVersion;

public VersionMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync(_version);

//we're all done, so don't invoke next middleware
}
}

在ASP.NET Core 2.x中,您可以在Startup.cs中通过使用Map()扩展方法来指定URL和对应的中间件:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

app.UseCors();

app.Map("/version", versionApp => versionApp.UseMiddleware<VersionMiddleware>());

app.UseMvcWithDefaultRoute();
}

当您使用/version开头的URL(/version/version/test)访问时,都获得相同的响应:

1.0.0

当您发送带有任何(除静态文件以外)的URL的请求时,将调用MvcMiddleware,并处理该请求。 使用上面的代码,CORS中间件无法知道哪个端点将最终被执行。

改中间件为端点路由

在ASP.NET Core 3.0中,我们使用端点路由,路由与端点的调用是分开的。 实际上,是有两个中间件:

  • EndpointRoutingMiddleware 称为路由中间件。根据请求的URL来计算由哪个端点来执行。
  • EndpointMiddleware 成为端点中间件,调用中间件。

它们分别添加在管道的两个不同的位置,起着两个不同的作用。 通常,您希望路由中间件更早的添加到管道中,以便后续的中间件可以获得端点(即将被执行地)的信息。 端点中间件应该在管道的最后。 例如:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

// Add the EndpointRoutingMiddleware
app.UseRouting();

// All middleware from here onwards know which endpoint will be invoked
app.UseCors();

// Execute the endpoint selected by the routing middleware
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}

扩展方法UseRouting()EndpointRoutingMiddleware添加到管道,而扩展方法UseEndpoints()EndpointMiddleware添加到管道。 通过使用UseEndpoints()会注册所有端点(在上面的示例中,我们仅注册我们的MVC控制器)。

注意:通常将静态文件中间件放在路由中间件之前。 这样可以避免在请求静态文件时有额外的路由开销。 如迁移文档中所述,将身份验证和授权controllers 放在两个中间件中间也很重要。

下面我们使用新的端点路由方式改写之前的VersionMiddleware

我们使用/version URL作为匹配路径,将注册版本端点的代码移到UseEndpoints()调用中:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

app.UseRouting();

app.UseCors();

app.UseEndpoints(endpoints =>
{
// Add a new endpoint that uses the VersionMiddleware
endpoints.Map("/version", endpoints.CreateApplicationBuilder()
.UseMiddleware<VersionMiddleware>()
.Build())
.WithDisplayName("Version number");

endpoints.MapDefaultControllerRoute();
});
}

有几个重点我们需要注意:

  • 我们使用 IApplicationBuilder ()来创建了RequestDelegate
  • 对路径的完整匹配,不再是前缀匹配方式。
  • 可以对端点设置一个显示名称(上面代码中的"Version number")
  • 可以附加额外元数据(上面代码中没有展示)

Map方法需要RequestDelegate而不是Action <IApplicationBuilder>。导致添加中间件为端点的代码要比之前2.x更加冗长。 可以写一个扩展方法来解决这个问题:

public static class VersionEndpointRouteBuilderExtensions
{
public static IEndpointConventionBuilder MapVersion(this IEndpointRouteBuilder endpoints, string pattern)
{
var pipeline = endpoints.CreateApplicationBuilder()
.UseMiddleware<VersionMiddleware>()
.Build();

return endpoints.Map(pattern, pipeline).WithDisplayName("Version number");
}
}

然后在Configure()中使用这个扩展方法让代码更加简洁:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

app.UseRouting();

app.UseCors();

// Execute the endpoint selected by the routing middleware
app.UseEndpoints(endpoints =>
{
endpoints.MapVersion("/version");
endpoints.MapDefaultControllerRoute();
});
}

另外一个重要差异是,在ASP.NET Core 2.x中,VersionMiddleware匹配/version为前缀的所有请求。 比如/version/version/123/version/test/oops等等。当改为使用端点路由时,并不是前缀匹配,而是完整匹配。 可以在端点路由中都使用路由参数。 例如:

endpoints.MapVersion("/version/{id:int?}");

这种写法可以匹配/version/version/123 URL,但不匹配/version/test/oops

端点路由的另外一个特性,可以将元数据库附加到端点上。

端点的另一个功能是可以将元数据附加到端点。 在前面的示例中,我们提供了一个显示名称(主要用于调试目的),您还可以附加更多信息,例如授权策略或CORS策略,供其它中间件查询。 例如:

public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();

app.UseRouting();

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapVersion("/version")
.RequireCors("AllowAllHosts")
.RequireAuthorization("AdminOnly");

endpoints.MapDefaultControllerRoute();
});
}

上面代码中,我们向Version端点中添加了CORS策略(AllowAllHosts)和授权策略(AdminOnly)。 当请求到达时,路由中间件选择Version端点,并附带了相关元数据, 当授权中间件和CORS中间件,发现Version端点存在这些策略时,会在Version端点执行前,先执行授权中间件和CORS中间件的逻辑。

必须把中间件改为端点路由吗

不是必须的。中间件方式的管道概念并没有变。您仍然可以像ASP.NET Core 1.0以后的版本一样,完全从中间件分支或短路返回。不需要使用端点路由来替换原理的方法。

但是,使用端点路由有3个优势:

  • 可以将元数据附加到端点,以便其它中间件(例如Authorization,CORS)可以知道最终端点是谁,该执行什么操作
  • 可以在非MVC的端点中使用路由模板,因此可以使用路由解析这个特性
  • 可以更方便地生成指向非MVC端点的URL

如果上述特性对您有用,那么端点路由非常适合您。 例如,ASP.NET Core HealthCheck已改为为端点路由,那么在请求健康状态Url时,添加授权检查。

如果您觉得这些特性没用,则没有理由不必转换为端点路由。 例如,静态文件中间件通常是短路响应的,没有将其转换为端点路由。 静态文件通常不需要将授权或CORS。

最重要的是,静态文件中间应将件放在路由中间件之前。

总体而言,端点路由在以前的路由方法中增加了许多特性,您需要在升级时注意差异。 如果尚未安装,请务必查看迁移指南,其中详细介绍了许多更改。

总结

在这篇文章中,我概述了ASP.NET Core中的路由及其发展历程,及路由的请求处理程序的执行分离后,带来的一些优势。

我还展示了,如何将ASP.NET Core 2.x应用程序中使用的简单末端中间件,转换为ASP.NET Core 3.0中的端点。 所需的更改相对较小,但需要注意的是支持路由参数的完整匹配替换了前缀匹配

文章作者: Akini Xu
文章链接: https://blog.ibestread.com/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 嘉阅
支付宝打赏
微信打赏