×

ASP.NET Core 自定义中间件,就这么简单

独孤求败 独孤求败 发表于2026-04-09 10:09:46 浏览12 评论0

抢沙发发表评论

HTTP 请求从客户端到服务器的处理过程,需经过一系列有序组件,这些组件构成中间件管道。ASP.NET Core 提供了众多内置中间件,但实际开发中,统一日志、全局异常处理等个性化需求无法被覆盖,此时自定义中间件成为最优解,既能保证业务清晰,又能提升系统可维护性。

一、什么是中间件?

中间件是介于 HTTP 请求与响应之间的可插拔组件,核心作用是过滤、处理请求和响应,具备四大核心能力:

  • 检查并修改 HTTP 请求或响应内容;

  • 决定是否将请求传递给下一个中间件;

  • 可在请求处理前后分别执行逻辑(如记录请求耗时);

  • 直接终止管道(如校验失败时返回 401 响应)。

其执行流程可简化为:请求 → 中间件 A → 中间件 B → 控制器 → 中间件 B → 中间件 A → 响应。关键细节:必须调用 _next(context) 才能将控制权移交下一个中间件①,否则请求会被阻断。

二、为何需要自定义中间件?

内置中间件仅覆盖基础场景,实际项目中以下个性化需求需通过自定义中间件实现,同时可减少代码冗余、提升复用性:

  • 统一日志记录:记录所有请求的方法、路径、响应状态码,便于排查问题;

  • 全局异常处理:替代控制器中重复的 try-catch,统一异常响应格式;

  • 安全校验:验证自定义 API 密钥、额外校验 JWT 令牌;

  • 性能监控:统计请求处理耗时,定位系统性能瓶颈。

自定义中间件可将这些跨业务的通用逻辑集中处理,实现代码解耦与复用②。

三、创建自定义中间件的步骤

创建自定义中间件核心为3步:定义类、注册、优化调用,实操性极强。

步骤一:定义中间件类

标准中间件类需满足两个核心要求:含接收 RequestDelegate 的构造函数,实现 InvokeAsync 核心方法。以“请求日志中间件”为例:

public classRequestLoggingMiddleware
{
    // 指向管道中下一个中间件
    privatereadonly RequestDelegate _next;

    // 构造函数注入下一个中间件
    public RequestLoggingMiddleware(RequestDelegate next) => _next = next;

    // 核心执行方法,处理请求和响应
    public async Task InvokeAsync(HttpContext context)
    {
        // 请求进入时记录请求信息
        Console.WriteLine($"→ {context.Request.Method} {context.Request.Path}");
        // 移交控制权给下一个中间件
        await _next(context);
        // 响应返回时记录状态码
        Console.WriteLine($"← {context.Response.StatusCode}");
    }
}

关键点:必须调用 _next(context),否则请求会被阻断,客户端将超时。

步骤二:注册中间件

定义完成后,需在 Program.cs 中注册,使其加入中间件管道:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// 注册自定义请求日志中间件
app.UseMiddleware<RequestLoggingMiddleware>();

app.MapControllers();
app.Run();

核心注意点:中间件的注册顺序,决定了它的执行顺序。安全相关中间件(异常处理、认证授权)必须放在最前端,确保所有请求都能被校验和异常捕获③。

步骤三:优化:使用扩展方法

多个自定义中间件会导致 Program.cs 代码繁琐,推荐封装扩展方法简化注册,符合 ASP.NET Core 编码规范:

// 扩展方法类
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder app)
        => app.UseMiddleware<RequestLoggingMiddleware>();
}

// Program.cs 中简洁注册
app.UseRequestLogging();

这种方式遵循 Fluent API 设计,提升代码可读性④。

四、实战示例:全局异常处理中间件

全局异常处理是自定义中间件最常用场景,可彻底解决 try-catch 冗余问题,完整实现如下:

public classExceptionHandlingMiddleware
{
    privatereadonly RequestDelegate _next;
    privatereadonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "系统发生未处理异常");
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new { error = "服务器内部错误,请稍后再试" });
        }
    }
}

核心优势:无需重复 try-catch、异常响应格式统一、自动记录异常日志(便于排查)⑤。

注册时需放在所有中间件最前端,确保捕获所有异常:

app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseRequestLogging();
app.MapControllers();

五、中间件执行顺序的重要性

中间件注册顺序直接决定执行顺序,错误顺序会导致功能异常。示例:

// 正确顺序:异常处理在前,日志在后
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseMiddleware<RequestLoggingMiddleware>();

若颠倒顺序,日志中间件抛出的异常无法被捕获,会导致系统直接返回 500 错误且无日志记录。铁律:安全相关中间件(异常、认证、授权)必须位于管道最前端⑥。

六、最佳实践与常见误区

推荐做法:

  • 轻量设计:中间件仅处理横切关注点,不执行数据库查询、复杂计算等耗时操作;

  • 依赖注入:通过构造函数注入日志、配置等服务,不手动创建实例;

  • 异步非阻塞InvokeAsync 必用 async/await,避免阻塞线程;

  • 单一职责:每个中间件仅处理一项逻辑(如日志只负责记录,异常只负责处理)。

常见误区:

  • ❌ 忘记调用 _next(context),导致请求阻断;

  • ❌ 中间件中写复杂业务逻辑,导致职责混乱、难以维护;

  • ❌ 忽略中间件自身异常处理,导致管道崩溃⑦。

七、典型应用场景

除日志、异常处理外,自定义中间件还有以下高频场景:

  • API 网关:统一处理鉴权、限流、协议转换;

  • 审计系统:记录用户敏感操作,便于追溯;

  • 多租户架构:解析请求中的租户标识,注入上下文;

  • 健康检查:拦截 /health 请求,返回服务运行状态⑧。

八、结语

自定义中间件是 ASP.NET Core 灵活性的核心,核心价值在于“解耦”——将横切关注点与业务逻辑分离,构建高内聚、低耦合的企业级应用。掌握其执行机制、注册顺序和最佳实践,就能灵活应对各类个性化需求,写出简洁可维护的代码。

群贤毕至

访客