欢迎光临
我们一直在努力

.NET Core WebAPI 中间件与过滤器(Filter)深度剖析:从原理到实战

.NET Core WebAPI 中间件与过滤器(Filter)深度剖析:从原理到实战

你是否曾经困惑过:中间件和过滤器都能处理HTTP请求,它们到底有什么区别?什么时候该用中间件,什么时候该用过滤器?本文将带你彻底搞清楚这两个概念,并通过实际案例让你轻松掌握。

写在前面

作为.NET开发者,我们每天都在和HTTP请求打交道。在ASP.NET Core中,处理请求有两个重要的概念:中间件(Middleware) 和过滤器(Filter)。很多初学者甚至一些有经验的开发者,对它们的区别和使用场景都存在模糊认识。

记得我刚接触ASP.NET Core时,也曾被这两个概念搞晕。经过多年实践,我总结了一套通俗易懂的理解方式。今天,就让我用最直白的语言,配合实际案例,帮你彻底搞懂它们。

一、概念解析

1. 中间件(Middleware)

中间件是ASP.NET Core请求处理管道中的一个个"关卡"。每个中间件都可以:

  • 在请求到达后续中间件之前执行一些操作
  • 决定是否将请求传递给下一个中间件
  • 在后续中间件处理完成后执行一些操作

可以把它想象成一个洋葱模型:

请求 →
[中间件1] →
[中间件2] →
[中间件3] →
处理程序
← [中间件3]
← [中间件2]
← [中间件1]
← 响应

2. 过滤器(Filter)

过滤器是MVC框架层面的概念,它依附于控制器和Action方法。过滤器允许你在Action执行的不同阶段插入自定义逻辑。

ASP.NET Core提供了五种类型的过滤器:

过滤器类型执行时机
Authorization Filter 授权验证,最早执行
Resource Filter 资源处理前后(模型绑定之前/之后)
Action Filter Action方法执行前后
Exception Filter 异常处理
Result Filter 结果执行前后

二、核心区别

为了让你更清晰地理解,我整理了一个对比表格:

对比维度中间件过滤器
所属层级 管道级别(全局) MVC框架级别
作用范围 所有请求 特定Controller/Action
访问上下文 只能访问HttpContext 可以访问MVC上下文(ModelState、Action参数等)
适用场景 通用HTTP处理(认证、日志、静态文件等) 业务逻辑处理(参数验证、结果格式化等)
执行顺序 按注册顺序 按过滤器类型和Order属性
是否依赖MVC

一句话总结

中间件管"管道",过滤器管"业务"

  • 中间件:处理所有进入管道的请求,不管你是访问API还是静态文件
  • 过滤器:只处理MVC相关的请求(Controller/Action),关注业务逻辑的横切关注点

三、深入源码分析

中间件的工作原理

中间件的本质是一个委托链。看一下UseMiddleware的简化实现:

public class ApplicationBuilder
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new();

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}

public RequestDelegate Build()
{
RequestDelegate app = context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
};

// 反向构建委托链
foreach (var component in _components.AsEnumerable().Reverse())
{
app = component(app);
}

return app;
}
}

每个中间件都接收一个RequestDelegate(下一个中间件),并返回一个新的RequestDelegate。

过滤器的执行流程

过滤器的执行依赖于MVC的ActionInvoker。当请求到达MVC中间件后,路由匹配到对应的Controller和Action,然后ActionInvoker会:

  • 执行Authorization Filter
  • 执行Resource Filter(OnResourceExecuting)
  • 执行Action Filter(OnActionExecuting)
  • 执行Action方法
  • 执行Action Filter(OnActionExecuted)
  • 执行Result Filter(OnResultExecuting)
  • 执行结果
  • 执行Result Filter(OnResultExecuted)
  • 执行Resource Filter(OnResourceExecuted)
  • 四、实战案例

    案例1:全局异常处理

    使用中间件实现:

    public class ExceptionHandlingMiddleware
    {
    private readonly RequestDelegate _next;
    private readonly 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";

    var response = new
    {
    Success = false,
    Message = "服务器内部错误",
    TraceId = context.TraceIdentifier
    };

    await context.Response.WriteAsync(JsonSerializer.Serialize(response));
    }
    }
    }

    // 注册方式
    app.UseMiddleware<ExceptionHandlingMiddleware>();

    使用过滤器实现:

    public class GlobalExceptionFilter : IExceptionFilter
    {
    private readonly ILogger<GlobalExceptionFilter> _logger;

    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
    {
    _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
    _logger.LogError(context.Exception, "Action执行发生异常");

    context.Result = new ObjectResult(new
    {
    Success = false,
    Message = context.Exception.Message, // 开发环境可以返回具体信息
    StackTrace = context.Exception.StackTrace
    })
    {
    StatusCode = StatusCodes.Status500InternalServerError
    };

    context.ExceptionHandled = true;
    }
    }

    // 注册方式
    builder.Services.AddControllers(options =>
    {
    options.Filters.Add<GlobalExceptionFilter>();
    });

    对比分析:

    • 中间件的异常处理更底层,会捕获所有请求(包括静态文件)的异常
    • 过滤器的异常处理只针对MVC请求,但可以访问MVC上下文,比如获取Action参数

    案例2:请求/响应日志记录

    使用中间件实现:

    public class RequestLoggingMiddleware
    {
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
    // 记录请求信息
    var request = context.Request;
    var requestBody = await ReadRequestBodyAsync(request);

    _logger.LogInformation("请求路径: {Path}, 方法: {Method}, Body: {Body}",
    request.Path, request.Method, requestBody);

    // 记录响应信息
    var originalBodyStream = context.Response.Body;
    using var responseBodyStream = new MemoryStream();
    context.Response.Body = responseBodyStream;

    await _next(context);

    context.Response.Body.Seek(0, SeekOrigin.Begin);
    var responseBody = await new StreamReader(context.Response.Body).ReadToEndAsync();
    context.Response.Body.Seek(0, SeekOrigin.Begin);

    _logger.LogInformation("响应状态码: {StatusCode}, Body: {Body}",
    context.Response.StatusCode, responseBody);

    await responseBodyStream.CopyToAsync(originalBodyStream);
    }

    private async Task<string> ReadRequestBodyAsync(HttpRequest request)
    {
    request.EnableBuffering();
    var body = await new StreamReader(request.Body).ReadToEndAsync();
    request.Body.Seek(0, SeekOrigin.Begin);
    return body;
    }
    }

    使用过滤器实现:

    public class ActionLoggingFilter : IActionFilter
    {
    private readonly ILogger<ActionLoggingFilter> _logger;

    public ActionLoggingFilter(ILogger<ActionLoggingFilter> logger)
    {
    _logger = logger;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
    var controllerName = context.Controller.GetType().Name;
    var actionName = context.ActionDescriptor.DisplayName;
    var parameters = context.ActionArguments;

    _logger.LogInformation("执行Action: {Controller}.{Action}, 参数: {@Parameters}",
    controllerName, actionName, parameters);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    var controllerName = context.Controller.GetType().Name;
    var actionName = context.ActionDescriptor.DisplayName;

    if (context.Exception != null)
    {
    _logger.LogError(context.Exception, "Action执行失败");
    }
    else
    {
    var result = context.Result;
    _logger.LogInformation("Action执行成功: {Controller}.{Action}, 结果: {Result}",
    controllerName, actionName, result);
    }
    }
    }

    对比分析:

    • 中间件可以记录完整的请求和响应内容(包括HTTP头、Body等)
    • 过滤器只能记录MVC层面信息,但可以访问Action参数模型,记录更业务化的日志

    案例3:接口权限验证

    使用中间件实现(JWT验证):

    public class JwtAuthenticationMiddleware
    {
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;

    public JwtAuthenticationMiddleware(RequestDelegate next, IConfiguration configuration)
    {
    _next = next;
    _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
    // 白名单路径跳过验证
    var path = context.Request.Path.Value;
    if (path == "/api/auth/login" || path == "/api/auth/register")
    {
    await _next(context);
    return;
    }

    var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");

    if (string.IsNullOrEmpty(token))
    {
    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
    await context.Response.WriteAsync("缺少认证Token");
    return;
    }

    try
    {
    // 验证JWT Token…
    await _next(context);
    }
    catch (Exception)
    {
    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
    await context.Response.WriteAsync("无效的Token");
    }
    }
    }

    使用过滤器实现(自定义权限验证):

    public class PermissionFilter : IAuthorizationFilter
    {
    private readonly string _requiredPermission;

    public PermissionFilter(string requiredPermission)
    {
    _requiredPermission = requiredPermission;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
    var user = context.HttpContext.User;

    if (!user.Identity.IsAuthenticated)
    {
    context.Result = new UnauthorizedResult();
    return;
    }

    // 获取用户权限列表
    var permissions = user.Claims
    .Where(c => c.Type == "Permission")
    .Select(c => c.Value)
    .ToList();

    if (!permissions.Contains(_requiredPermission))
    {
    context.Result = new ForbidResult();
    return;
    }
    }
    }

    // 使用方式
    [PermissionFilter("User.Delete")]
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteUser(int id)
    {
    // 只有拥有User.Delete权限的用户才能执行
    }

    对比分析:

    • 中间件适合做统一的认证(如JWT验证),针对所有请求
    • 过滤器适合做细粒度的授权,可以针对不同的Action配置不同的权限要求

    五、最佳实践指南

    什么时候使用中间件?

    ✅ 适合中间件的场景:

    • 跨所有请求的通用处理(认证、日志、异常捕获)
    • 处理静态文件
    • 压缩/加密请求和响应
    • 跨域处理(CORS)
    • 请求限流
    • 自定义路由

    ❌ 不适合中间件的场景:

    • 需要访问MVC特定功能(ModelState、Action参数)
    • 需要根据Controller/Action做差异化处理
    • 需要访问Action的返回值

    什么时候使用过滤器?

    ✅ 适合过滤器的场景:

    • Action参数的验证和预处理
    • Action执行前后的业务逻辑
    • 统一的结果格式化
    • 细粒度的权限控制
    • 缓存控制
    • 事务管理(如EF Core的事务)

    ❌ 不适合过滤器的场景:

    • 处理非MVC请求(如静态文件)
    • 需要在整个管道层面做处理
    • 处理原始HTTP请求/响应流

    执行顺序的注意事项

    注册顺序很重要!举个实际例子:

    // Program.cs
    var app = builder.Build();

    // 1. 异常处理中间件(最先注册,最后执行)
    app.UseMiddleware<ExceptionHandlingMiddleware>();

    // 2. 认证中间件
    app.UseMiddleware<AuthenticationMiddleware>();

    // 3. 日志中间件
    app.UseMiddleware<LoggingMiddleware>();

    // 4. MVC中间件(最后注册,最先执行)
    app.MapControllers();

    app.Run();

    执行顺序:日志 → 认证 → 异常处理 → 请求 → 异常处理(返回)→ 认证(返回)→ 日志(返回)

    六、进阶技巧

    1. 中间件和过滤器的结合使用

    实际项目中,我通常这样组合使用:

    // 中间件处理:全局异常、JWT认证、请求日志
    app.UseMiddleware<ExceptionHandlingMiddleware>();
    app.UseMiddleware<JwtAuthenticationMiddleware>();
    app.UseMiddleware<RequestLoggingMiddleware>();

    // 过滤器处理:权限验证、参数校验、事务管理
    builder.Services.AddControllers(options =>
    {
    options.Filters.Add<PermissionFilter>();
    options.Filters.Add<ValidationFilter>();
    options.Filters.Add<TransactionFilter>();
    });

    2. 自定义中间件的最佳实践

    // 推荐:使用扩展方法简化注册
    public static class MiddlewareExtensions
    {
    public static IApplicationBuilder UseCustomAuth(this IApplicationBuilder app)
    {
    return app.UseMiddleware<CustomAuthMiddleware>();
    }
    }

    // 使用
    app.UseCustomAuth();

    3. 依赖注入的区别

    • 中间件:通过构造函数注入,生命周期为Singleton或Scoped(Invoke方法参数可以获取Scoped服务)
    • 过滤器:可以通过ServiceFilter或TypeFilter属性注入,支持更灵活的DI

    // 中间件获取Scoped服务
    public async Task InvokeAsync(HttpContext context, IUserService userService)
    {
    // userService 是Scoped生命周期
    }

    // 过滤器使用ServiceFilter
    [ServiceFilter(typeof(UserService))]
    public class UserController : ControllerBase
    {
    // 自动注入
    }

    七、总结

    核心要点

  • 本质区别:中间件在管道层面工作,过滤器在MVC框架层面工作
  • 作用范围:中间件影响所有请求,过滤器只影响MVC请求
  • 使用场景:中间件做"管道"的事,过滤器做"业务"的事
  • 执行顺序:中间件按注册顺序执行,过滤器有固定的生命周期顺序
  • 快速决策表

    需求推荐方案
    统一异常处理 都可以,范围广用中间件,需要MVC上下文用过滤器
    JWT认证 中间件
    细粒度权限控制 过滤器(Authorization Filter)
    请求日志 中间件(记录原始数据)
    Action参数验证 过滤器(Action Filter)
    跨域处理 中间件
    响应缓存 过滤器(Result Filter)
    处理静态文件 中间件

    一句话记忆

    中间件是"管道工",负责整个HTTP管道的疏通;过滤器是"质检员",负责MVC业务环节的品质把控。


    希望这篇文章能帮你彻底理解中间件和过滤器的区别。在实际开发中,选择合适的工具能让你的代码更优雅、更高效。如果还有其他疑问,欢迎在评论区交流讨论!

    下期预告:我们将深入探讨如何在ASP.NET Core中实现优雅的全局事务管理,敬请期待!


    如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、分享!你的支持是我持续创作的动力。 🚀

    赞(0)
    未经允许不得转载:171主机测评 » .NET Core WebAPI 中间件与过滤器(Filter)深度剖析:从原理到实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址