何时使用ASP.NET Core中间件,何时它会成为代价高昂的反模式

作者:微信公众号:【架构师老卢】
8-2 8:20
15

中间件是ASP.NET Core中最受欢迎的功能之一。它让你能够集中控制请求/响应处理,而且极其灵活。只需几行app.Use(...)代码,你就能实现日志记录、身份验证或CORS功能。

但问题在于:中间件并非总是解决问题的最佳工具。

试图在中间件中塞入过多逻辑会导致代码混乱、路由出错以及应用程序脆弱不堪。要构建清晰、可维护的API,你需要知道中间件在哪些方面表现出色,而在哪些方面则不尽如人意。

让我们来详细分析一下。

中间件的真正用途是什么?

本质上,中间件是一个函数,它可以在HTTP请求/响应流经ASP.NET Core管道时对其进行检查、修改或短路处理。

每个中间件组件:

  • 可以访问HttpContext
  • 可以在调用下一个组件之前和之后执行操作
  • 默认情况下是全局的——会在每个请求上运行

这是一个经典示例:

app.Use(async (context, next) =>
{
    Console.WriteLine("Before");
    await next.Invoke();
    Console.WriteLine("After");
});

执行流程会自上而下地穿过管道,然后再回溯——可以把它想象成嵌套的try...finally语句。

当你希望不考虑端点逻辑而对请求进行广泛控制时,就可以使用中间件。

(图片将被放大显示)

什么是中间件

中间件何时非常适用

中间件在处理横切关注点时表现出色——这些逻辑应该在每个请求上运行,并且不依赖于路由或模型绑定。

最适合的使用场景:

  • 日志记录和诊断 捕获开始/结束时间戳、状态码、请求体(需谨慎处理)。
  • 全局异常处理 用try-catch包裹下游逻辑,并记录结构化错误。
  • 标头操作 统一添加或修改标头(例如,X-Correlation-ID、CORS)。
  • 区域性设置/本地化 使用标头或Cookie设置当前线程的区域性。
  • JWT提取 在管道早期将轻量级令牌信息解析为声明。

示例:日志记录中间件

public class LoggingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
        await next(context);
        Console.WriteLine($"Response: {context.Response.StatusCode}");
    }
}

然后在Startup.cs中注册它:

builder.Services.AddTransient<LoggingMiddleware>();
app.UseMiddleware<LoggingMiddleware>();

简单、全局、清晰。这正是中间件的最佳应用方式。

(图片将被放大显示)

中间件何时会成为坏主意

尽管中间件功能强大,但它并非万能药。它无法访问许多你经常需要的东西——比如路由参数、模型状态或复杂的依赖注入图。

不要将中间件用于:

  • 基于路由的决策 中间件在路由之前运行。context.GetRouteValue("id")通常会返回null,除非你重新排序管道或明确使用端点路由。
  • 授权策略 策略通常依赖于用户角色、声明和路由数据。应该使用AuthorizeAttribute或过滤器。
  • 验证 你无法访问模型绑定结果或[Required]错误。应该使用ActionFilters或FluentValidation。
  • 需要许多作用域服务的复杂逻辑 中间件可以通过IMiddleware接受作用域依赖项,但内联lambda表达式(app.Use(...))是单例的,不能使用像EF Core DbContext这样的作用域服务。

反模式示例

app.Use(async (context, next) =>
{
    var id = context.GetRouteValue("id"); // 可能为null
    if (id?.ToString() == "admin")
    {
        // 尝试在这里注入DbContext会导致错误💥
    }
    await next();
});

这是很脆弱的,很容易出错——尤其是当路由发生变化时。

比较表

使用此表作为选择逻辑放置位置的参考指南。

精通中间件的专业技巧

💡 当你需要作用域服务时,注册IMiddleware

builder.Services.AddTransient<MyMiddleware>();
app.UseMiddleware<MyMiddleware>();

💡 避免基于URL片段或查询字符串控制流程 应该使用路由或操作过滤器。

💡 组合小型、单一职责的中间件 将中间件分解为原子单元,例如:

  • RequestLoggingMiddleware
  • ExceptionHandlingMiddleware
  • CorrelationIdMiddleware

这会使你的管道保持清晰且易于调试。

中间件是ASP.NET Core请求生命周期中一个强大的早期钩子,但它并非适用于所有情况。

使用中间件的场景:

  • 全局的横切关注点
  • 无状态逻辑
  • 请求/响应塑形

避免使用中间件的场景:

  • 与路由相关的逻辑
  • 模型验证
  • 任何需要深层依赖注入树的情况

构建清晰的管道,而不是逻辑块。让过滤器和控制器去做它们设计用来做的事情。

相关留言评论
昵称:
邮箱:
阅读排行