ASP.NET Core Filters详解:原理、执行顺序与DI使用全解析

概述

关于过滤器,我不知道大家了解多少,不过如果了解中间件的话,那么理解这个过滤器也是非常简单的。

过滤器和中间件有很多的相似之处,构成上:前逻辑、后逻辑、next;还有过滤器管道、中间件管道。

既然与中间件如此相似但又不是中间件,那么肯定是有区别的:

机制 层级 拦截范围 典型用途
Middleware HTTP 管道 所有请求,包括静态文件 CORS、限流、认证、全局日志、异常处理
Filter MVC 内部 仅 Controller / Action 权限校验(Authorization)、输入参数验证、结果包装、Action 日志、AOP 风格拦截

注:Middleware 拦截的是整个 HTTP 请求,而 Filter 只针对 MVC 的 Controller/Action。

过滤器类型

ASP.NET Core 提供 5 种主要过滤器类型:

类型 接口 执行阶段 典型应用
授权过滤器 IAuthorizationFilter / IAsyncAuthorizationFilter 动作前 权限验证、身份校验
资源过滤器 IResourceFilter / IAsyncResourceFilter 动作前后 缓存、资源预处理
动作过滤器 IActionFilter / IAsyncActionFilter 动作前后 日志、模型验证
异常过滤器 IExceptionFilter / IAsyncExceptionFilter 异常发生时 捕获异常并处理返回结果
结果过滤器 IResultFilter / IAsyncResultFilter 动作后 修改或包装返回结果

过滤器在Http管道的位置

那么过滤器在请求管道中的位置如图:

filter-pipeline-1

既然刚才看到了,前逻辑、后逻辑、next这三个连在一起出现,那么我相信大多数人第一眼想到的应该是洋葱模型吧,没错,过滤器也有它的洋葱模型

当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。
全局筛选器包围类筛选器,而类筛选器又包围方法筛选器。(类型优先级固定(授权 → 资源 → 动作 → 结果)同类型内部按注册顺序执行)
在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:

  • 全局筛选器的 before 代码。
    • 控制器筛选器的 before 代码。
      • 操作方法筛选器的 before 代码。
      • 操作方法筛选器的 after 代码。
    • 控制器筛选器的 after 代码。
  • 全局筛选器的 after 代码。

注:异常过滤器(Exception)不参与正常顺序,它只在抛出未捕获异常时触发。

而它的管道画出来大概就是这样:

屏幕截图 2026-04-08 173901

Filter实现

这里我先给一个关于 过滤器 注册的表格,现在看不懂没有关系

依赖DI 不依赖DI 说明
全局 builder.Services.AddScoped();
builder.Services.AddControllers(options =>{options.Filters.AddService();});
options.Filters.Add(); 全局过滤器会作用于所有 Controller 和 Action;有参构造必须用 AddService()
非全局 builder.Services.AddScoped(); 在过滤器定义类的实现接口位置添加 Attribute 注意放在第一位 Attribute 用于定义特性类,方便控制器或方法添加该特性

注:Attribute 用于定义特性类,让控制器或者相关的方法可以添加该特性
注:依赖 DI 的过滤器需要注册到容器(AddScoped / AddTransient / AddSingleton 均可,具体取决于使用场景)
注:有参全局过滤器必须使用 AddService(),无参是Add<>()

除了在AddControllers()中注册全局过滤器,也可以在其他地方注册:

方法 支持的功能 常用场景 过滤器注册效果
AddControllers() 仅支持 API Controller([ApiController]) Web API 可以注册全局过滤器,但只影响 API 控制器方法
AddControllersWithViews() API + MVC View 支持 传统 MVC 注册全局过滤器,可作用于 Controller + ViewAction
AddMvc() API + MVC + RazorPages 全支持 综合应用 注册全局过滤器,作用最广

全局-依赖DI

GlobalLogFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Microsoft.AspNetCore.Mvc.Filters;

namespace TestFilter01x02.Filters
{
public class GlobalLogFilter: IAsyncActionFilter
{
private readonly ILogger<GlobalLogFilter> _logger;
public GlobalLogFilter(ILogger<GlobalLogFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_logger.LogInformation("全局-依赖DI, Action 即将执行...");

var resultContext = await next();

_logger.LogInformation("全局-依赖DI, Action 已经执行完成...");
}
}
}

program:

1
2
3
4
5
6
7
8
9
//全局-依赖DI
// 注册 DI,这个 GlobalLogFilter 构造方法有参数,所以需要注册 DI,才能在全局过滤器中使用
builder.Services.AddScoped<GlobalLogFilter>();

builder.Services.AddControllers(options =>
{
//全局-依赖DI
options.Filters.AddService<GlobalLogFilter>();
});

操作方法:

1
2
3
4
5
6
[HttpPost("Test01")]
//测试全局过滤器
public async Task<ApiResponse<string>> TestActionFilterAsync(string number)
{
return ApiResponse<string>.Ok();
}

运行截图:

屏幕截图 2026-04-08 181730

全局-不依赖DI

GlobalFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.AspNetCore.Mvc.Filters;

namespace TestFilter01x02.Filters
{
public class GlobalFilter: IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("全局-不依赖DI, Action 即将执行...");

var resultContext = await next();

Console.WriteLine("全局-不依赖DI, Action 已经执行完成...");
}
}
}

program:

1
2
3
4
5
6
builder.Services.AddControllers(options =>
{
//全局-不依赖DI
options.Filters.Add<GlobalFilter>();

});

全局过滤器作用于所有 Controller/Action;非全局只作用于指定 Controller 或方法。
控制器不变,看一下两份代码有什么区别?
首先第一个过滤器的不同, GlobalLogFilter 它的过滤器是构造函数含参ILogger类型,它有了 DI容器的依赖;
第二个不同,program 中
前者:builder.Services.AddScoped(); 后者则没有这个

除此之外的不同

方法 DI 支持 构造函数限制 场景
options.Filters.Add<T>() ❌ 不走 DI 必须无参构造函数 全局非 DI 过滤器、Attribute 过滤器
options.Filters.AddService<T>() ✔ 走 DI 可以有依赖参数 全局 DI 过滤器、复杂日志、数据库操作等

注:AddService 注册的全局过滤器依赖 DI 生命周期(Scoped/Singleton/Transient)和服务容器。

非全局-依赖DI

NonGlobalLogFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Microsoft.AspNetCore.Mvc.Filters;

namespace TestFilter01x02.Filters
{
public class NonGlobalLogFilter : IAsyncActionFilter
{
private readonly ILogger<NonGlobalLogFilter> _logger;
public NonGlobalLogFilter(ILogger<NonGlobalLogFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
_logger.LogInformation("非全局-依赖DI, Action 即将执行…");
var resultContext = await next();
_logger.LogInformation("非全局-依赖DI, Action 已经执行完成…");
}
}
}

program:

1
2
//非全局-依赖DI
builder.Services.AddScoped<NonGlobalLogFilter>();

非全局-不依赖DI

NonGlobalFilter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Microsoft.AspNetCore.Mvc.Filters;

namespace TestFilter01x02.Filters
{
public class NonGlobalFilter : Attribute, IAsyncActionFilter
{

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("非全局-不依赖DI, Action 即将执行...");
// 执行 Action
var resultContext = await next();
Console.WriteLine("非全局-不依赖DI, Action 已经执行完成...");
}
}
}

Attribute 这个接口了,是用来定义特性类的,且需要放在定义类的接口的第一个位置
注:有参构造的过滤器一定要走 DI,否则无法注入服务。
program:

1
//没有需要添加的

这个两个非全局过滤器的使用:
TestController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Microsoft.AspNetCore.Http;
using TestFilter01x02.Filters;
using Microsoft.AspNetCore.Mvc;

namespace TestFilter01x02.Controllers
{
[Route("api/[controller]")]
[ApiController]
[NonGlobalFilter]//这里
public class TestController : ControllerBase
{

[HttpPost("Test03")]
[ServiceFilter(typeof(NonGlobalLogFilter))]
//测试非全局过滤器-依赖DI,这里
public ApiResponse<string> TestAsyncActionFilter03(string number)
{
return ApiResponse<string>.Ok();
}
}
}


区别是特性标签不同:一个是[ServiceFilter(typeof(NonGlobalLogFilter))] ,另一个是[NonGlobalFilter]
你也可以把它们的位置换过来.

运行截图:

屏幕截图 2026-04-08 184301

从图里面看,这也确实是一个洋葱模型

总结

ASP.NET Core Filters 是 MVC 内部的“轻量级管道”,类似洋葱模型。
类型优先级固定:授权 → 资源 → 动作 → 结果。
DI 支持与否影响注册方式和构造函数限制。
全局/非全局决定作用范围。
通过合理组合,可以实现日志、权限、缓存、异常处理等功能。