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是一次性提交,后续仅需获取计算结果,可采用此模式:
- 客户端发送POST请求(带大payload),服务器计算后将结果存入缓存,生成唯一临时键(如GUID),返回302重定向到
/api/Calculate/Result?key=临时键 - 客户端跟随重定向发送GET请求,该请求可被浏览器正常缓存,后续重复请求时浏览器会自动返回304
- 服务器端需关联临时键与用户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
相关产品推荐
相关产品推荐

