etag 是服务器生成的资源唯一标识符,比 last-modified 更精确,不依赖时间精度、不受时钟同步影响、可区分同内容不同修改时间的资源;在 asp.net core 中应基于内容生成强 etag,手动验证 if-none-match 并返回 304,httpclient 需手动处理 etag 缓存逻辑,部署时需注意 iis/kestrel/nginx 对 etag 头的干扰。

ETag 是什么,为什么不能只靠 Last-Modified
ETag 是服务器为资源生成的唯一标识符(比如 "abc123" 或 W/"def456"),比 Last-Modified 更精确:它不依赖时间精度(秒级)、不惧时钟不同步、还能区分内容相同但修改时间不同的资源。如果你的 C# 后端返回了 ETag,客户端下次请求带上 If-None-Match,服务端就能直接返回 304,跳过文件读取和响应体传输。
ASP.NET Core 中如何正确生成和验证 ETag
别手动拼接哈希或用 DateTime.UtcNow.ToString() 当 ETag —— 这既不安全也不高效。真实场景下应基于文件内容(或关键元数据)生成强 ETag:
- 对静态文件,启用内置支持:
app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = ctx => { ctx.Context.Response.GetTypedHeaders().ETag = new EntityTagHeaderValue($"\"{GetContentHash(ctx.File.PhysicalPath)}\""); } }); - 对动态文件下载(如
FileStreamResult),在 Action 里计算并设置:Response.Headers.ETag = new EntityTagHeaderValue($"\"{ComputeMd5Hash(filePath)}\""); - 必须检查
If-None-Match并提前返回 304:if (Request.Headers.IfNoneMatch.Any(etag => etag.Equals(Response.Headers.ETag))) { Response.StatusCode = StatusCodes.Status304NotModified; return; } - 注意:若用了
W/前缀(弱校验),EntityTagHeaderValue会自动处理;但你自己拼字符串时千万别漏掉引号,否则浏览器直接忽略
HttpClient 下载时如何触发 ETag 缓存验证
HttpClient 默认不缓存、也不自动发 If-None-Match。你得手动加请求头,并自己处理 304:
- 先发一次 GET 拿到响应头里的
ETag:var etag = response.Headers.ETag?.ToString(); - 下次请求前设置:
request.Headers.IfNoneMatch.Add(EntityTagHeaderValue.Parse(etag)); - 收到 304 后,**不能直接用 response.Content.ReadAsByteArrayAsync()** —— 它会抛
InvalidOperationException,因为 304 响应没有 body。你要提前判断:if (response.StatusCode == HttpStatusCode.NotModified) { /* 复用本地缓存 */ } - 别指望
HttpClient自动重用连接或缓存响应体;ETag 验证逻辑必须由你控制,包括本地磁盘缓存策略
常见坑:IIS / Kestrel 的中间件顺序和响应截断
ETag 失效往往不是代码问题,而是部署环境干扰:
- IIS 默认启用动态内容压缩,可能在写响应前就截断了
ETag头——在web.config里关掉:<urlcompression dostaticcompression="true" dodynamiccompression="false"></urlcompression> - Kestrel + 反向代理(如 Nginx)时,代理可能清除或覆盖
ETag;确认它透传了ETag、If-None-Match和304状态码 - 用
Response.OnStarting动态加 ETag?危险——此时 headers 可能已发送。务必在 action 执行早期、甚至 filter 里设置 - 文件很大时,别在内存里算全量 MD5;改用流式哈希(
MD5.Create().ComputeHash(stream)),但要确保 stream 可重置(stream.CanSeek)
ETag 的核心不在“怎么设”,而在“谁负责比对、谁决定返回 304、谁保管上次的 ETag 值”。这三个角色一旦脱节,缓存就形同虚设。










