接口 IResultFilter、IAsyncResultFilter 的简介和用法示例(.net)
本文首先简单介绍了两个接口 IResultFilter、IAsyncResultFilter 的定义,然后通过示例详解了几个涉及到的方法,供参考。
〇、IResultFilter、IAsyncResultFilter 接口简介
IResultFilter 是 ASP.NET Core MVC 管道中一个非常重要的过滤器接口,它主要是
在操作结果(IActionResult)被执行之前和之后,来执行自定义逻辑
。这里的“操作结果”指的是控制器动作方法返回的 IActionResult 实例,例如 ViewResult、JsonResult、RedirectResult、ContentResult 等。
接口定义:
#region 程序集 Microsoft.AspNetCore.Mvc.Abstractions, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 // C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.10\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Abstractions.dll #endregion namespace Microsoft.AspNetCore.Mvc.Filters { // 摘要:一个过滤器,用于在操作完成后对结果的加工 public interface IResultFilter : IFilterMetadata { // 摘要:在操作结果【执行前】调用 void OnResultExecuting(ResultExecutingContext context); // 摘要:在操作结果【执行后】调用 void OnResultExecuted(ResultExecutedContext context); } // 异步版本【推荐使用】 public interface IAsyncResultFilter : IFilterMetadata { Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); } }
由于 IAsyncResultFilter 提供了更好的异步支持(避免了 async void 的坑),并且可以更灵活地控制执行流程(通过调用 next()),通常建议实现 IAsyncResultFilter 而不是 IResultFilter。
IResultFilter 可以用于:
修改操作结果:
在结果执行前,可以检查、修改甚至替换
即将执行的 IActionResult。在结果执行后执行逻辑:
在结果(如视图渲染完成、JSON 序列化完成、重定向发生后)执行完毕后,执行一些清理、日志记录或审计
操作。短路结果执行:
在 OnResultExecuting 中,通过设置 ResultExecutingContext.Result 并调用 ResultExecutingContext.Cancel = true,可以完全跳过原始结果的执行,直接返回一个新的结果
。重要区别:不要将
IResultFilter
与IActionFilter
混淆。IActionFilter
作用于控制器动作方法本身
的执行前后(OnActionExecuting, OnActionExecuted)。IResultFilter
作用于动作方法返回的结果
(IActionResult)的执行前后。
一、方法 void OnResultExecuting(ResultExecutingContext context):操作结果执行前
1.1 简介
调用时机:
在框架准备执行 IActionResult
(例如,开始渲染视图或序列化 JSON)之前
立即调用。// 执行大概顺序: AuthorizationFilter ↓ ResourceFilter ↓ ActionFilter (OnActionExecuting) ↓ Controller Action Method Executes ↓ ActionFilter (OnActionExecuted) ↓ IResultFilter (OnResultExecuting) // ← 【在这里】 ↓ IActionResult Executes (e.g., View renders, JSON serializes) ↓ IResultFilter (OnResultExecuted) ↓ ExceptionFilter ↓ ResourceFilter
此时 Action 已经执行完毕,IActionResult 对象已经创建,但结果(如视图、JSON 响应)尚未执行或写入响应流。此时可以修改或替换即将执行的 IActionResult。
主要用途:
检查/修改 IActionResult:
通过 context.Result 获取或设置即将执行的结果。可以修改它的属性,或者完全替换它。短路执行:
这是 IResultFilter 最强大的功能之一。可以通过创建一个新的 IActionResult(如 ContentResult, JsonResult, RedirectResult 等),将其赋值给 context.Result,然后设置 context.Cancel = true,这将阻止原始结果的执行,框架会立即执行你提供的新结果。执行前置逻辑:
如记录日志、验证某些条件、向 HttpContext.Response 添加响应头等。
ResultExecutingContext 参数:
- context.
Result
: 表示即将被执行的 IActionResult。
-
- 读取它(例如,检查返回的是 JsonResult 还是 ViewResult)。
- 修改它(例如,替换为另一个 IActionResult,如将 JsonResult 改为 ContentResult)。
- context.
Cancel
:通过设置 context.Result 为一个新结果并调用 context.Cancel = true,可以阻止原始结果的执行,并立即返回你设置的结果。 - context.
Controller
:获取当前控制器实例。 - context.
HttpContext
:获取当前 HTTP 上下文,可用于访问请求、响应、会话等。 - context.
Canceled
:指示执行是否已被取消(通常由其他筛选器设置)。 - context.
ActionDescriptor
:提供关于当前执行的动作的信息。 - context.
ModelState
:提供关于模型验证状态的信息。
1.2 简单的示例:添加自定义响应头
public class AddHeaderResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { // 在结果执行前添加一个自定义响应头 context.HttpContext.Response.Headers.Add("X-Custom-Header", "MyValue"); // 继续执行原始结果 } public void OnResultExecuted(ResultExecutedContext context) { // 结果执行后可以记录日志 Console.WriteLine($"Result '{context.Result}' executed for {context.HttpContext.Request.Path}"); } }
1.3 示例:短路结果执行
public class MaintenanceModeResultFilter : IResultFilter { private readonly bool _isInMaintenanceMode; public MaintenanceModeResultFilter(bool isInMaintenanceMode) { _isInMaintenanceMode = isInMaintenanceMode; } public void OnResultExecuting(ResultExecutingContext context) { if (_isInMaintenanceMode) { // 创建一个维护模式的响应结果 var maintenanceResult = new ContentResult { Content = "<h1>网站正在维护中,请稍后再试。</h1>", ContentType = "text/html" }; // 将新结果赋值给 context context.Result = maintenanceResult; // 取消原始结果的执行 context.Cancel = true; } // 如果不在维护模式,不设置 Cancel,原始结果将继续执行 } public void OnResultExecuted(ResultExecutedContext context) { // 如果被短路,这里仍然会执行 if (context.Canceled) { Console.WriteLine("Result execution was canceled (Maintenance Mode)."); } } }
1.4 示例:记录结果执行时间
public class TimingResultFilter : IResultFilter { private const string StopwatchKey = "ResultExecutionStopwatch"; public void OnResultExecuting(ResultExecutingContext context) { // 开始计时 var stopwatch = Stopwatch.StartNew(); context.HttpContext.Items[StopwatchKey] = stopwatch; } public void OnResultExecuted(ResultExecutedContext context) { // 停止计时并记录 if (context.HttpContext.Items[StopwatchKey] is Stopwatch stopwatch) { stopwatch.Stop(); Console.WriteLine($"Result '{context.Result.GetType().Name}' executed in {stopwatch.ElapsedMilliseconds}ms."); } } }
二、方法 void OnResultExecuted(ResultExecutedContext context):操作结果执行后
2.1 简介
调用时机:
在 IActionResult 已经执行完毕之后调用
。如果 OnResultExecuting 中发生了异常,或者执行被短路(context.Cancel = true),这个方法仍然会执行。执行顺序,详见本文章节:1.1。
主要用途:
执行后置逻辑:
如记录日志、审计、清理资源。检查执行结果:
通过 context.Result 可以查看最终执行的是哪个结果(可能是原始结果,也可能是 OnResultExecuting 中设置的新结果)。检查异常:
通过 context.Exception 可以检查在结果执行过程中是否发生了未处理的异常(如果 context.Exception 不为 null)。注意,如果异常被处理了(例如在过滤器中捕获并设置了结果),Exception 可能为 null。
ResultExecutedContext 参数:
context.
Result
:获取最终执行的 IActionResult(在 OnResultExecuting 中可能已被修改)。context.
Exception
:获取在结果执行过程中发生的未处理异常。如果为 null,表示没有异常。context.
HttpContext
, context.ActionDescriptor
, context.ModelState
:与 ResultExecutingContext 中的同名属性作用相同。context.
Canceled
:如果执行在 OnResultExecuting 中被取消(context.Cancel = true),则此属性为 true。2.2 示例:记录日志
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using System.Diagnostics; // 实现 IResultFilter 接口 public class SimpleLoggingResultFilter : IResultFilter { private readonly ILogger<SimpleLoggingResultFilter> _logger; private readonly Stopwatch _stopwatch; // 用于计算执行时间 // 通过依赖注入获取 ILogger public SimpleLoggingResultFilter(ILogger<SimpleLoggingResultFilter> logger) { _logger = logger; _stopwatch = new Stopwatch(); } // IResultFilter.OnResultExecuting: 在 Result 执行前调用 // 这里我们用它来启动计时器 public void OnResultExecuting(ResultExecutingContext context) { _stopwatch.Restart(); // 重置并启动计时器 _logger.LogDebug($"准备执行结果: {context.HttpContext.Request.Path}"); } // IResultFilter.OnResultExecuted: 在 Result 执行后调用 // 这是记录最终日志的主要方法 public void OnResultExecuted(ResultExecutedContext context) { _stopwatch.Stop(); // 停止计时器 var httpContext = context.HttpContext; var request = httpContext.Request; var response = httpContext.Response; // 获取状态码 int statusCode = response.StatusCode; // 构建日志消息 var logMessage = $"请求完成 - " + $"路径: {request.Path}, " + $"方法: {request.Method}, " + $"状态码: {statusCode}, " + $"耗时: {_stopwatch.ElapsedMilliseconds}ms"; // 根据状态码选择日志级别 if (statusCode >= 500) { _logger.LogError(logMessage); } else if (statusCode >= 400) { _logger.LogWarning(logMessage); } else { _logger.LogInformation(logMessage); } // 如果 Result 执行过程中发生了未处理的异常 if (context.Exception != null) { _logger.LogError(context.Exception, "Result 执行中发生未处理异常"); } // 注意: OnResultExecuted 之后,响应通常已经发送给客户端 // 在这里修改 context.Result 或设置跳过 (context.Canceled = true) 通常无效或可能导致错误 // 因为响应流可能已经关闭。 } }
三、方法 Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next):异步方式【推荐使用】
3.1 简介
OnResultExecutionAsync 是 IAsyncResultFilter 接口的核心方法,它
可以取代旧的同步 IResultFilter 接口,提供了更灵活、更强大的异步处理能力
。ResultExecutingContext context:
这个 context 对象封装了 IActionResult 即将执行时的环境信息。几个常用参数:
context.
Result
: 这是即将被执行的 IActionResult 实例(如 ViewResult, JsonResult, RedirectResult 等)。你可以读取、修改这个属性,甚至替换它。context.
HttpContext
: 提供对当前 HTTP 请求和响应的完全访问。这是你操作响应头、读取请求信息、访问会话等的主要途径。context.
Controller
: 指向执行该结果的控制器实例。context.
Canceled
: 一个 bool 属性。如果设置为 true,它会短路 (short-circuit) 后续的筛选器管道和 IActionResult 的执行。注意:仅仅设置 context.Canceled = true 并不会自动产生响应;你通常需要同时设置 context.Result 来提供一个替代的响应。ResultExecutionDelegate next:
这是一个委托(delegate),本质上是一个 Func<Task<ResultExecutedContext>>。调用 await next() 会继续执行筛选器管道,最终导致 IActionResult 被实际执行(例如,视图被渲染,JSON 被序列化)。
await next() 的返回值是一个 ResultExecutedContext 对象,它包含了 IActionResult 执行完成后的状态信息。
执行流程与 next() 的关键作用
为什么说 OnResultExecutionAsync 的执行流程是环绕式 (around) 的?
前处理 (Before):
你的代码首先执行 await next() 之前的逻辑。这对应于旧的 OnResultExecuting 阶段。调用 next():
await next() 调用是核心。
-
- 触发剩余的 IResultFilter/IAsyncResultFilter 的 OnResultExecutionAsync 方法(如果存在)。
- 最终执行 IActionResult.ExecuteResultAsync (或同步版本)。
- 返回一个 ResultExecutedContext。
后处理 (After):
await next() 完成后,你的代码继续执行 await next() 之后的逻辑。这对应于旧的 OnResultExecuted 阶段。此时,IActionResult 已经执行完毕。
next() 的强大之处在于:它可以
精确控制代码在结果执行前和后的运行
,而且开发者还可以选择不调用 next()
,从而完全阻止原始 IActionResult 的执行。ResultExecutedContext(来自 await next())
当 await next() 完成后,可以得到一个 ResultExecutedContext。它的关键属性包括:
context.
Result
: 执行后的 IActionResult(可能在管道中被修改过)。context.
Exception
: 如果在 IActionResult 执行过程中或之后的筛选器中抛出了未处理的异常,这里会包含该异常。context.
ExceptionHandled
: 一个 bool,表示异常是否已被某个筛选器标记为已处理。如果 context.Exception != null && !context.ExceptionHandled,说明有一个未处理的异常。context.
Canceled
: 表示执行是否被取消(通常由前面的筛选器设置)。context.
HttpContext
: 执行完成后的上下文。3.2 示例一:添加自定义响应头
如下,是最常见的用法之一。在结果执行前设置响应头,确保所有通过 MVC 返回的响应都包含这些安全或元数据头。
using Microsoft.AspNetCore.Mvc.Filters; using System.Threading.Tasks; public class SecurityHeadersFilter : IAsyncResultFilter { public async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { var response = context.HttpContext.Response; // 添加安全相关的响应头 response.Headers.Add("X-Content-Type-Options", "nosniff"); response.Headers.Add("X-Frame-Options", "DENY"); response.Headers.Add("X-XSS-Protection", "1; mode=block"); // 注意:Content-Security-Policy 非常复杂,这里只是简单示例 response.Headers.Add("Content-Security-Policy", "default-src 'self'"); // 添加自定义头 response.Headers.Add("X-Generated-By", "My ASP.NET Core App"); // 继续执行后续的筛选器和 IActionResult await next(); } }
3.3 示例二:响应结果包装(API 版本化或统一格式)
如下,过滤器将所有 JsonResult 的响应体包装在一个包含元数据(如成功标志、时间戳)的通用结构中。这对于构建 RESTful API 非常有用,可以提供一致的响应格式。
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System.Threading.Tasks; public class ApiResponseWrapperFilter : IAsyncResultFilter { public async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { // 1. 检查当前结果是否是 JsonResult (我们只包装 JSON 响应) if (context.Result is not JsonResult) { // 不是 JSON,直接继续执行 await next(); return; } // 2. 准备包装对象 var originalResult = context.Result as JsonResult; var wrappedResponse = new { Success = true, // 假设操作成功 Timestamp = DateTime.UtcNow, Data = originalResult.Value, // 将原始数据放入包装对象的 Data 属性 // Version = "1.0" // 可以加入 API 版本信息 }; // 3. 替换 context.Result 为新的 JsonResult context.Result = new JsonResult(wrappedResponse) { // 保持原始 JsonResult 的序列化设置 (如 JsonSerializerOptions) SerializerSettings = (originalResult as JsonResult)?.SerializerSettings }; // 4. 继续执行。现在执行的是我们包装后的 JsonResult await next(); } } // 使用示例控制器 [ApiController] [Route("api/[controller]")] public class TestController : ControllerBase { [HttpGet] public IActionResult GetData() { // 返回的匿名对象会被 ApiResponseWrapperFilter 包装 return Ok(new { Name = "John", Age = 30 }); // 最终响应体: { "Success": true, "Timestamp": "...", "Data": { "Name": "John", "Age": 30 } } } }
3.4 示例三:基于条件的短路 (Short-circuiting) - 简单缓存
如下,过滤器演示了强大的短路能力。它在 IActionResult 执行前检查缓存,如果命中,则直接用缓存的结果替换 context.Result 并设置 context.Cancel = true,从而跳过昂贵的 IActionResult 执行过程(如数据库查询、视图渲染)。如果未命中,则执行 next(),并在执行成功后将结果存入缓存。
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Caching.Memory; using System.Threading.Tasks; public class SimpleCacheFilter : IAsyncResultFilter { private readonly IMemoryCache _cache; public SimpleCacheFilter(IMemoryCache cache) { cache = cache; } public async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { // 1. 为当前请求生成一个缓存键 (简化示例,实际中需要更健壮的键) var cacheKey = $"Result{context.HttpContext.Request.Path}"; // 2. 尝试从缓存中获取结果 if (_cache.TryGetValue(cacheKey, out object cachedResult)) { // 3. 缓存命中!短路执行 // 将缓存的结果设置为 context.Result context.Result = cachedResult as IActionResult; // 标记为已取消,阻止 next() 执行 context.Cancel = true; // 记录命中日志 Console.WriteLine($"Cache HIT for {cacheKey}"); return; // 直接返回,不执行 next() } // 4. 缓存未命中,继续执行原始的 IActionResult // 注意:我们调用 next(),它会返回 ResultExecutedContext var executedContext = await next(); // 5. 检查执行是否成功且没有异常/取消 if (!executedContext.Canceled && executedContext.Exception == null) { // 6. 将执行后的结果(注意是 context.Result,不是 executedContext.Result)存入缓存 // (假设我们信任 context.Result 在执行后是有效的) _cache.Set(cacheKey, context.Result, TimeSpan.FromMinutes(5)); Console.WriteLine($"Cache SET for {cacheKey}"); } // 如果执行被取消或有异常,通常不缓存 } }
3.5 示例四:性能监控(测量 IActionResult 执行时间)
如下,过滤器精确测量了 IActionResult 本身(不包括动作方法执行时间)的执行耗时。这对于识别性能瓶颈非常有用。try/finally 确保即使发生异常也能记录时间。
using Microsoft.AspNetCore.Mvc.Filters; using System.Diagnostics; using System.Threading.Tasks; public class PerformanceMonitorFilter : IAsyncResultFilter { public async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { var stopwatch = Stopwatch.StartNew(); try { // 执行 IActionResult await next(); } finally { stopwatch.Stop(); var elapsedMs = stopwatch.ElapsedMilliseconds; // 获取请求信息 var requestPath = context.HttpContext.Request.Path; var httpMethod = context.HttpContext.Request.Method; var statusCode = context.HttpContext.Response.StatusCode; // 记录性能日志 (这里用 Console 代替实际的日志框架) Console.WriteLine($"[{httpMethod}] {requestPath} -> Status: {statusCode}, " + $"Result Execution Time: {elapsedMs}ms"); // 在实际应用中,这里可能会发送到 Application Insights, Prometheus, 或写入日志文件 } } }
四、过滤器的注册
注册可以在三个地方实现,如下:
// 在 Program.cs 中注册 var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add<ApiResponseWrapperFilter>(); options.Filters.Add<AddHeaderFilter>(); }); // 在 Controller/Action 上使用特性 [ServiceFilter(typeof(ApiResponseWrapperFilter))] public class HomeController : Controller { // ... }

本文来自博客园,作者:橙子家,欢迎微信扫码关注博主【橙子家czzj】,有任何疑问欢迎沟通,共同成长!
转载本文请注明原文链接:https://www.cnblogs.com/hnzhengfy/p/19057533/IResultFilter
评论