ResponseCacheAttribute导致非200响应被缓存的问题求解
解决ASP.NET Core中ResponseCacheAttribute缓存非200状态码的问题
问题描述
使用Microsoft.AspNetCore.Mvc.Core提供的[ResponseCache(CacheProfileName = "Default")]标记控制器或动作方法时,非200状态码(如400、403、500)的响应会被客户端缓存,导致重复请求直接读取本地缓存,不会触发服务器端逻辑。官方文档说明响应缓存中间件仅缓存200响应,但ResponseCacheAttribute会为所有状态码添加缓存响应头,不符合仅缓存成功响应的需求。
解决方案
方法1:自定义条件缓存过滤器(推荐)
创建自定义过滤器,仅在响应状态码为200时应用缓存策略,非200状态码强制禁止缓存:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; public class ConditionalResponseCacheAttribute : ActionFilterAttribute { public string CacheProfileName { get; set; } public override void OnResultExecuted(ResultExecutedContext context) { if (context.HttpContext.Response.StatusCode == StatusCodes.Status200OK) { // 从MvcOptions中获取配置的缓存Profile var cacheProfiles = context.HttpContext.RequestServices .GetRequiredService<IOptions<MvcOptions>>() .Value .CacheProfiles; if (cacheProfiles.TryGetValue(CacheProfileName, out var cacheProfile)) { // 构建Cache-Control头 var cacheControlParts = new List<string>(); if (cacheProfile.NoStore) { cacheControlParts.Add("no-store"); } else { cacheControlParts.Add(cacheProfile.Location switch { ResponseCacheLocation.Any => "public", ResponseCacheLocation.Client => "private", _ => "no-cache" }); if (cacheProfile.Duration.HasValue) { cacheControlParts.Add($"max-age={cacheProfile.Duration.Value}"); } } context.HttpContext.Response.Headers.CacheControl = string.Join(", ", cacheControlParts); // 设置Vary头(如果配置了) if (!string.IsNullOrEmpty(cacheProfile.VaryByHeader)) { context.HttpContext.Response.Headers.Vary = cacheProfile.VaryByHeader; } // 设置其他缓存相关头(如Expires) if (cacheProfile.Duration.HasValue && !cacheProfile.NoStore) { context.HttpContext.Response.Headers.Expires = DateTimeOffset.UtcNow.AddSeconds(cacheProfile.Duration.Value).ToString("R"); } } } else { // 非200状态码时,强制禁用缓存 context.HttpContext.Response.Headers.CacheControl = "no-cache, no-store, must-revalidate"; context.HttpContext.Response.Headers.Pragma = "no-cache"; context.HttpContext.Response.Headers.Expires = "0"; } base.OnResultExecuted(context); } }
用法:将原来的[ResponseCache(CacheProfileName = "Default")]替换为自定义过滤器:
[ConditionalResponseCache(CacheProfileName = "Default")] public IActionResult YourAction() { // 业务逻辑 if (someErrorCondition) { return StatusCode(500); } return Ok(data); }
方法2:在动作方法中动态控制缓存头
如果仅需要在个别动作方法中处理,可以直接在方法内根据返回状态码设置响应头:
public IActionResult YourAction() { try { // 执行成功逻辑 var data = FetchData(); // 应用缓存配置 var cacheProfile = HttpContext.RequestServices .GetRequiredService<IOptions<MvcOptions>>() .Value .CacheProfiles["Default"]; Response.Headers.CacheControl = $"public, max-age={cacheProfile.Duration}"; if (!string.IsNullOrEmpty(cacheProfile.VaryByHeader)) { Response.Headers.Vary = cacheProfile.VaryByHeader; } return Ok(data); } catch { // 错误时禁用缓存 Response.Headers.CacheControl = "no-cache, no-store, must-revalidate"; Response.Headers.Pragma = "no-cache"; Response.Headers.Expires = "0"; return StatusCode(500); } }
关键说明
- 自定义过滤器方式可在多个控制器/动作方法中复用,保持代码一致性;
- 强制禁用缓存的响应头会告诉客户端不要缓存该响应,确保每次请求都触发服务器端逻辑;
- 若使用响应缓存中间件,需确保中间件配置不会覆盖自定义的缓存头逻辑。
内容的提问来源于stack exchange,提问作者stephan.peters
相关产品推荐
相关产品推荐

