过滤器管道源码解析
大约 8 分钟约 2467 字
过滤器管道源码解析
简介
ASP.NET Core 的过滤器(Filters)是 MVC/Controller 架构中的管道扩展机制,在 Action 执行前后提供拦截点。理解过滤器的执行顺序、管道构建原理和过滤器工厂模式,有助于正确实现认证、缓存、日志等横切关注点。
特点
过滤器类型体系
五种过滤器
// ASP.NET Core 过滤器类型(按执行顺序排列):
// 1. IAuthorizationFilter — 授权过滤器(最先执行)
// 2. IResourceFilter — 资源过滤器(管道外层)
// 3. IActionFilter — Action 过滤器(包裹 Action)
// 4. IExceptionFilter — 异常过滤器(捕获异常)
// 5. IResultFilter — 结果过滤器(包裹结果)
// 过滤器执行管道:
// → IAuthorizationFilter.OnAuthorization
// → IResourceFilter.OnResourceExecuting
// → Model Binding
// → IActionFilter.OnActionExecuting
// → Action Method 执行
// → IActionFilter.OnActionExecuted
// → IResultFilter.OnResultExecuting
// → Result 执行(序列化等)
// → IResultFilter.OnResultExecuted
// → IResourceFilter.OnResourceExecuted
// ← IExceptionFilter(异常时触发)
// 过滤器作用域:
// 1. 全局 — 注册时添加(对所有 Controller/Action 生效)
// 2. Controller — [Attribute] 标注在 Controller 类上
// 3. Action — [Attribute] 标注在 Action 方法上
// 执行顺序:全局 → Controller → Action授权过滤器
// IAuthorizationFilter — 最先执行,决定是否允许访问
public class ApiKeyAuthorizationFilter : IAuthorizationFilter
{
private readonly IConfiguration _config;
public ApiKeyAuthorizationFilter(IConfiguration config)
{
_config = config;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.TryGetValue("X-Api-Key", out var apiKey))
{
context.Result = new UnauthorizedResult(); // 短路
return;
}
var validKey = _config["ApiKey"];
if (apiKey != validKey)
{
context.Result = new ForbidResult(); // 短路
return;
}
// 通过,继续执行后续过滤器
}
}
// 注册
builder.Services.AddControllers(options =>
{
options.Filters.Add<ApiKeyAuthorizationFilter>();
});
// 或者使用 [Authorize] 属性
[Authorize(Roles = "Admin")]
[HttpGet("admin/dashboard")]
public IActionResult Dashboard() => Ok();资源过滤器
// IResourceFilter / IAsyncResourceFilter — 在管道最外层,适合缓存
public class ResponseCacheFilter : IAsyncResourceFilter
{
private readonly IMemoryCache _cache;
private readonly ILogger<ResponseCacheFilter> _logger;
private static readonly SemaphoreSlim _semaphore = new(1, 1);
public ResponseCacheFilter(IMemoryCache cache, ILogger<ResponseCacheFilter> logger)
{
_cache = cache;
_logger = logger;
}
public async Task OnResourceExecutionAsync(
ResourceExecutingContext context,
ResourceExecutionDelegate next)
{
var cacheKey = GenerateCacheKey(context.HttpContext.Request);
// 尝试从缓存获取
if (_cache.TryGetValue(cacheKey, out CachedResponse? cached))
{
_logger.LogDebug("Cache hit: {Key}", cacheKey);
context.Result = new ObjectResult(cached!.Data)
{
StatusCode = cached.StatusCode
};
return; // 短路,不执行 Action
}
// 缓存未命中,执行 Action
var executedContext = await next();
// 缓存成功响应
if (executedContext.Result is ObjectResult { StatusCode: >= 200 and < 300 } result)
{
var options = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
_cache.Set(cacheKey, new CachedResponse(result.Value, result.StatusCode ?? 200), options);
}
}
private static string GenerateCacheKey(HttpRequest request)
=> $"{request.Method}:{request.Path}:{request.QueryString}";
private record CachedResponse(object? Data, int StatusCode);
}
// 使用
[ServiceFilter(typeof(ResponseCacheFilter))]
[HttpGet("products")]
public IActionResult GetProducts() => Ok(_productService.GetAll());Action 过滤器
// IActionFilter / IAsyncActionFilter — Action 执行前后
public class RequestLoggingFilter : IAsyncActionFilter
{
private readonly ILogger<RequestLoggingFilter> _logger;
public RequestLoggingFilter(ILogger<RequestLoggingFilter> logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
var stopwatch = Stopwatch.StartNew();
var requestId = Guid.NewGuid().ToString("N")[..8];
// Action 执行前
_logger.LogInformation(
"[{RequestId}] {Method} {Path} → Action: {Action}",
requestId,
context.HttpContext.Request.Method,
context.HttpContext.Request.Path,
context.ActionDescriptor.DisplayName);
// 记录参数
foreach (var (key, value) in context.ActionArguments)
{
_logger.LogDebug("[{RequestId}] Param: {Key} = {Value}", requestId, key, value);
}
// 执行 Action
var executedContext = await next();
stopwatch.Stop();
// Action 执行后
if (executedContext.Exception != null)
{
_logger.LogError(executedContext.Exception,
"[{RequestId}] Action failed in {Elapsed}ms",
requestId, stopwatch.ElapsedMilliseconds);
}
else
{
var statusCode = (executedContext.Result as ObjectResult)?.StatusCode ?? 200;
_logger.LogInformation(
"[{RequestId}] {StatusCode} in {Elapsed}ms",
requestId, statusCode, stopwatch.ElapsedMilliseconds);
}
}
}
// 参数验证过滤器
public class ValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState
.Where(kvp => kvp.Value?.Errors.Count > 0)
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value!.Errors.Select(e => e.ErrorMessage).ToArray());
context.Result = new BadRequestObjectResult(new
{
Type = "https://example.com/errors/validation",
Title = "Validation Failed",
Status = 400,
Errors = errors
});
}
}
public void OnActionExecuted(ActionExecutedContext context) { }
}异常过滤器
// IExceptionFilter — 捕获 Action 中的异常
public class DomainExceptionFilter : IExceptionFilter
{
private readonly ILogger<DomainExceptionFilter> _logger;
private readonly IHostEnvironment _env;
public DomainExceptionFilter(ILogger<DomainExceptionFilter> logger, IHostEnvironment env)
{
_logger = logger;
_env = env;
}
public void OnException(ExceptionContext context)
{
var exception = context.Exception;
var problem = exception switch
{
DomainException dex => new ProblemDetails
{
Type = $"https://example.com/errors/{dex.Code}",
Title = "Business Rule Violation",
Status = 400,
Detail = dex.Message
},
NotFoundException => new ProblemDetails
{
Type = "https://example.com/errors/not-found",
Title = "Not Found",
Status = 404,
Detail = exception.Message
},
ConcurrentModificationException => new ProblemDetails
{
Type = "https://example.com/errors/conflict",
Title = "Conflict",
Status = 409,
Detail = "The resource was modified by another request."
},
_ => new ProblemDetails
{
Type = "https://example.com/errors/internal",
Title = "Internal Server Error",
Status = 500,
Detail = _env.IsDevelopment() ? exception.Message : "An error occurred."
}
};
problem.Extensions["traceId"] = context.HttpContext.TraceIdentifier;
_logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);
context.Result = new ObjectResult(problem)
{
StatusCode = problem.Status
};
context.ExceptionHandled = true;
}
}结果过滤器
// IResultFilter — 结果执行前后
public class ETagResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(
ResultExecutingContext context,
ResultExecutionDelegate next)
{
// 结果执行前
if (context.Result is ObjectResult { Value: not null } objectResult)
{
// 序列化结果计算 ETag
var json = JsonSerializer.Serialize(objectResult.Value);
var hash = Convert.ToBase64String(
SHA256.HashData(Encoding.UTF8.GetBytes(json)));
var etag = $"\"{hash}\"";
// 检查 If-None-Match
if (context.HttpContext.Request.Headers.IfNoneMatch == etag)
{
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
return;
}
// 添加 ETag 头
context.HttpContext.Response.Headers.ETag = etag;
}
await next();
}
}
// 响应头过滤器
public class SecurityHeadersFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.XContentTypeOptions = "nosniff";
context.HttpContext.Response.Headers.XFrameOptions = "DENY";
context.HttpContext.Response.Headers.Add(
"Content-Security-Policy", "default-src 'self'");
}
public void OnResultExecuted(ResultExecutedContext context) { }
}过滤器工厂
IFilterFactory 实现
// IFilterFactory — 支持依赖注入的过滤器工厂
// 直接使用 [TypeFilter] 或 [ServiceFilter] 注入 DI 服务
// TypeFilter — 每次请求创建实例
[TypeFilter(typeof(RequestLoggingFilter))]
[HttpGet("users")]
public IActionResult GetUsers() => Ok();
// ServiceFilter — 从 DI 容器获取(需注册)
// 必须先注册:services.AddScoped<RequestLoggingFilter>();
[ServiceFilter(typeof(RequestLoggingFilter))]
[HttpGet("orders")]
public IActionResult GetOrders() => Ok();
// 自定义 IFilterFactory
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CacheFilterAttribute : Attribute, IFilterFactory
{
public int DurationSeconds { get; set; } = 300;
public bool VaryByQuery { get; set; } = true;
// 是否可复用(false = 每次创建新实例)
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var cache = serviceProvider.GetRequiredService<IMemoryCache>();
var logger = serviceProvider.GetRequiredService<ILogger<CacheFilter>>();
return new CacheFilter(cache, logger, DurationSeconds, VaryByQuery);
}
}
// 使用自定义属性
[CacheFilter(DurationSeconds = 60, VaryByQuery = true)]
[HttpGet("products")]
public IActionResult GetProducts(string? category, int page = 1) => Ok();
// 缓存过滤器实现
public class CacheFilter : IAsyncResourceFilter
{
private readonly IMemoryCache _cache;
private readonly ILogger<CacheFilter> _logger;
private readonly int _duration;
private readonly bool _varyByQuery;
public CacheFilter(IMemoryCache cache, ILogger<CacheFilter> logger,
int duration, bool varyByQuery)
{
_cache = cache;
_logger = logger;
_duration = duration;
_varyByQuery = varyByQuery;
}
public async Task OnResourceExecutionAsync(
ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var key = BuildCacheKey(context);
if (_cache.TryGetValue(key, out ObjectResult? cached))
{
context.Result = cached;
return;
}
var result = await next();
if (result.Result is ObjectResult { StatusCode: >= 200 and < 300 } obj)
{
_cache.Set(key, obj, TimeSpan.FromSeconds(_duration));
}
}
private string BuildCacheKey(ResourceExecutingContext context)
{
var req = context.HttpContext.Request;
return _varyByQuery
? $"{req.Method}:{req.Path}:{req.QueryString}"
: $"{req.Method}:{req.Path}";
}
}过滤器执行顺序控制
Order 控制与管道嵌套
// 过滤器执行顺序规则:
// 1. 按 Order 升序执行(默认 0)
// 2. 相同 Order:全局 → Controller → Action
// 3. Executed 阶段反向执行(洋葱模型)
// 显式控制顺序
[AuthorizationFilter(Order = 1)] // 最先执行
[ResourceFilter(Order = 2)]
[ActionFilter(Order = 3)] // 最后执行
[HttpGet("data")]
public IActionResult GetData() => Ok();
// 过滤器管道嵌套示意:
// Request →
// Auth(1) → Auth(2) → Resource(1) → Resource(2) →
// Action(1) → Action(2) → [Action] → Action(2) → Action(1) →
// Result(1) → Result(2) → [Result] → Result(2) → Result(1) →
// Resource(2) → Resource(1) → Auth(2) → Auth(1)
// ← Response
// 短路示例
public class ShortCircuitFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Headers.TryGetValue("X-Maintenance", out var val)
&& val == "true")
{
// 短路:直接返回 503,不执行 Action
context.Result = new StatusCodeResult(503);
}
}
public void OnActionExecuted(ActionExecutedContext context) { }
}优点
缺点
总结
过滤器管道按类型分层执行:Authorization → Resource → Action → Result,异常过滤器在异常时触发。IResourceFilter 适合缓存(在管道最外层),IActionFilter 适合日志和参数验证,IExceptionFilter 适合全局异常处理,IResultFilter 适合 ETag 和响应头修改。自定义过滤器通过 [TypeFilter] 或 [ServiceFilter] 注入 DI 服务,IFilterFactory 支持参数化过滤器创建。执行顺序通过 Order 属性控制,短路通过设置 context.Result 实现。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《过滤器管道源码解析》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《过滤器管道源码解析》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《过滤器管道源码解析》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《过滤器管道源码解析》最大的收益和代价分别是什么?
