You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 06:35:19