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

System.Web.Http.ApiController POST请求浏览器缓存实现问题

问题分析与解决方案

首先明确核心限制:HTTP规范默认不缓存POST请求,浏览器不会自动存储POST的响应内容,这也是你用$.ajax收到304状态但拿不到response的根本原因——浏览器没缓存POST响应,jQuery也无法从本地获取数据返回给你。

下面给出几种可行的解决思路,适配你的业务场景(大payload、依赖用户数据变更的缓存刷新):


方案1:客户端维护缓存+服务器304校验

既然浏览器不缓存POST,我们自己在客户端用localStorage/sessionStorage存响应,结合服务器的304判断来验证缓存有效性,完美匹配你的需求:

客户端代码(jQuery)

// 生成请求唯一标识:payload哈希+当前用户ID,确保不同用户/不同请求的缓存隔离
function getRequestCacheKey(payload, userId) {
  const payloadStr = JSON.stringify(payload);
  // 简单哈希实现,生产环境可改用crypto-js等专业库
  const hash = payloadStr.split('').reduce((acc, char) => {
    acc = ((acc << 5) - acc) + char.charCodeAt(0);
    return acc & acc;
  }, 0);
  return `calc_cache_${userId}_${hash}`;
}

// 业务逻辑
const currentUserId = "当前登录用户ID"; // 从登录态获取
const requestPayload = {/* 你的大请求体 */};
const cacheKey = getRequestCacheKey(requestPayload, currentUserId);
const cachedRecord = JSON.parse(localStorage.getItem(cacheKey) || "{}");

$.ajax({
  url: "/api/Calculate",
  method: "POST",
  data: JSON.stringify(requestPayload),
  contentType: "application/json",
  headers: {
    // 带上缓存的校验标识,供服务器判断是否返回304
    ...(cachedRecord.etag && { "If-None-Match": cachedRecord.etag }),
    ...(cachedRecord.lastModified && { "If-Modified-Since": cachedRecord.lastModified })
  },
  success: function(response, status, jqXHR) {
    if (status === "notmodified") {
      // 服务器返回304,直接使用本地缓存的响应
      handleCalculationResult(cachedRecord.response);
    } else {
      // 拿到新响应,更新本地缓存
      const etag = jqXHR.getResponseHeader("ETag");
      const lastModified = jqXHR.getResponseHeader("Last-Modified");
      localStorage.setItem(cacheKey, JSON.stringify({
        response,
        etag,
        lastModified,
        timestamp: Date.now()
      }));
      handleCalculationResult(response);
    }
  }
});

function handleCalculationResult(data) {
  // 处理计算结果的业务逻辑
}

服务器端代码(C#)

public HttpResponseMessage Calculate([FromBody] CalculationRequest request)
{
    var userId = User.Identity.GetUserId();
    // 生成缓存键:用户ID+请求内容哈希,确保服务器端缓存的唯一性
    var serverCacheKey = $"calc_{userId}_{GetSha256Hash(JsonConvert.SerializeObject(request))}";
    var cachedData = ServerCache.Get<CachedCalculationResult>(serverCacheKey);
    
    // 生成校验用的ETag和LastModified
    var lastModified = cachedData?.LastModified ?? DateTime.UtcNow;
    var etag = $"\"{GetSha256Hash(cachedData?.ResultJson + lastModified.ToString("o"))}\"";

    // 校验请求头,判断是否返回304
    var ifNoneMatch = Request.Headers.IfNoneMatch.FirstOrDefault()?.Tag;
    var ifModifiedSince = Request.Headers.IfModifiedSince;
    if ((!string.IsNullOrEmpty(ifNoneMatch) && ifNoneMatch == etag) ||
        (ifModifiedSince.HasValue && ifModifiedSince.Value >= lastModified))
    {
        return Request.CreateResponse(HttpStatusCode.NotModified);
    }

    // 执行计算逻辑
    var calculationResult = PerformHeavyCalculation(request);
    var resultJson = JsonConvert.SerializeObject(calculationResult);

    // 更新服务器端缓存
    ServerCache.Set(serverCacheKey, new CachedCalculationResult {
        ResultJson = resultJson,
        LastModified = DateTime.UtcNow
    });

    // 返回响应并带上缓存校验头
    var response = Request.CreateResponse(HttpStatusCode.OK, calculationResult);
    response.Headers.ETag = new EntityTagHeaderValue(etag);
    response.Content.Headers.LastModified = lastModified;
    // 告诉浏览器不要自动缓存,由我们自己维护客户端缓存
    response.Headers.CacheControl = new CacheControlHeaderValue {
        NoCache = true,
        MaxAge = TimeSpan.Zero
    };

    return response;
}

// 辅助方法:生成SHA256哈希
private string GetSha256Hash(string input)
{
    using (var sha256 = SHA256.Create())
    {
        var bytes = Encoding.UTF8.GetBytes(input);
        var hashBytes = sha256.ComputeHash(bytes);
        return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
    }
}

// 服务器缓存实体类
public class CachedCalculationResult
{
    public string ResultJson { get; set; }
    public DateTime LastModified { get; set; }
}

方案2:POST-Redirect-GET(PRG)模式

如果大payload是一次性提交,后续仅需获取计算结果,可采用此模式:

  1. 客户端发送POST请求(带大payload),服务器计算后将结果存入缓存,生成唯一临时键(如GUID),返回302重定向到/api/Calculate/Result?key=临时键
  2. 客户端跟随重定向发送GET请求,该请求可被浏览器正常缓存,后续重复请求时浏览器会自动返回304
  3. 服务器端需关联临时键与用户ID,防止越权访问,并设置缓存有效期

方案3:改用Fetch API替代jQuery.ajax

jQuery对POST请求的304处理逻辑有局限性,Fetch API的行为更透明,可直接获取304状态并自行处理缓存:

const currentUserId = "当前登录用户ID";
const requestPayload = {/* 你的大请求体 */};
const cacheKey = getRequestCacheKey(requestPayload, currentUserId);
const cachedRecord = JSON.parse(localStorage.getItem(cacheKey) || "{}");

fetch("/api/Calculate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    ...(cachedRecord.etag && { "If-None-Match": cachedRecord.etag })
  },
  body: JSON.stringify(requestPayload)
})
.then(response => {
  if (response.status === 304) {
    // 304状态直接返回缓存数据
    return cachedRecord.response;
  } else if (response.ok) {
    // 新响应更新缓存
    return response.json().then(data => {
      const etag = response.headers.get("ETag");
      localStorage.setItem(cacheKey, JSON.stringify({
        response: data,
        etag,
        timestamp: Date.now()
      }));
      return data;
    });
  }
  throw new Error("请求失败");
})
.then(data => handleCalculationResult(data));

关键注意事项

  • 用户数据变更处理:监听storage事件(其他页面修改用户数据时触发),主动清理当前页面的相关缓存,确保缓存有效性
  • 存储容量:localStorage单域名容量约5MB,若计算结果过大,可改用IndexedDB存储
  • HTTP规范兼容:POST请求缓存是非标准行为,不要依赖浏览器自动缓存,必须自行维护客户端缓存

内容的提问来源于stack exchange,提问作者Terry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:55:22