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 灵活性的核心,核心价值在于“解耦”——将横切关注点与业务逻辑分离,构建高内聚、低耦合的企业级应用。掌握其执行机制、注册顺序和最佳实践,就能灵活应对各类个性化需求,写出简洁可维护的代码。