ASP.NET Core 中的响应缓存中间件
作者:John Luo 和 Rick Anderson
本文介绍如何在 ASP.NET Core 应用中配置响应缓存中间件。 中间件确定何时可缓存响应、存储响应并提供来自缓存的响应。 有关 HTTP 缓存和 [ResponseCache]
属性的简介,请参阅响应缓存。
响应缓存中间件:
- 启用基于 HTTP 缓存头的缓存服务器响应。 实现标准 HTTP 缓存语义。 像代理一样基于 HTTP 缓存标头进行缓存。
- 通过对 Razor Pages 等 UI 应用没有好处,因为浏览器通常会设置阻止缓存的请求头。 ASP.NET Core 7.0 及更高版本中提供的输出缓存将有利于 UI 应用。 使用输出缓存,配置可决定了应独立于 HTTP 标头缓存的内容。
- 对于来自满足缓存条件的客户端的公共 GET 或 HEAD API 请求可能有用。
若要测试响应缓存,请使用 Fiddler 或可显式设置请求头的其他工具。 显式设置标头是测试缓存的首选项。 有关详细信息,请参阅疑难解答。
配置
在中 Program.cs
,将响应缓存中间件服务 AddResponseCaching 添加到服务集合,并配置应用,以将中间件与 UseResponseCaching 扩展方法结合使用。 UseResponseCaching
将中间件添加到请求处理管道:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCaching();
var app = builder.Build();
app.UseHttpsRedirection();
// UseCors must be called before UseResponseCaching
//app.UseCors();
app.UseResponseCaching();
警告
使用 CORS 中间件时,必须在 UseResponseCaching 之前调用 UseCors。
示例应用添加标头来控制对后续请求的缓存:
- Cache-Control:缓存可缓存响应长达10秒。
- Vary:将中间件配置为仅当后续请求的 Accept-Encoding 标头与原始请求头匹配时才提供缓存的响应。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCaching();
var app = builder.Build();
app.UseHttpsRedirection();
// UseCors must be called before UseResponseCaching
//app.UseCors();
app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };
await next();
});
app.MapGet("/", () => DateTime.Now.Millisecond);
app.Run();
前面的标头不会写入响应,并当控制器、操作或 Razor 页面存在以下情况时被覆盖:
- 具有 [ResponseCache] 属性。 即使未设置属性,情况也是如此。 例如,省略 VaryByHeader 属性将导致从响应中删除相应的标头。
响应缓存中间件仅缓存导致 200 (OK) 状态代码的服务器响应。 中间件忽略包含错误页在内的任何其他响应。
警告
包含经过身份验证的客户端内容的响应必须标记为不可缓存,以防中间件存储和提供这些响应。 有关中间件如何确定响应是否可缓存的详细信息,请参阅缓存条件。
前面的代码通常不会将缓存的值返回到浏览器。 使用 Fiddler 或可显式设置请求头并作为用于测试缓存的首选项的其他工具。 有关详细信息,请参阅本文中的故障排除。
选项
下表中显示了响应缓存选项。
选项 | 说明 |
---|---|
MaximumBodySize | 响应正文的最大可缓存大小(以字节为单位)。 默认值为 64 * 1024 * 1024 (64 MB)。 |
SizeLimit | 响应缓存中间件的大小限制(以字节为单位)。 默认值为 100 * 1024 * 1024 (100 MB)。 |
UseCaseSensitivePaths | 确定是否将响应缓存在区分大小写的路径上。 默认值为 false 。 |
下面的示例将中间件配置为:
- 缓存正文大小小于或等于 1,024 字节的响应。
- 按区分大小写的路径存储响应。 例如,分别存储
/page1
和/Page1
。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCaching(options =>
{
options.MaximumBodySize = 1024;
options.UseCaseSensitivePaths = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
// UseCors must be called before UseResponseCaching
//app.UseCors();
app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };
await next(context);
});
app.MapGet("/", () => DateTime.Now.Millisecond);
app.Run();
VaryByQueryKeys
使用 MVC、Web API 控制器或 Razor Pages 页面模型时,[ResponseCache]
属性指定为响应缓存设置适当标头所需的参数。 [ResponseCache]
属性中要求必须具有中间件的唯一参数是 VaryByQueryKeys,该参数不对应实际的 HTTP 头。 有关详细信息,请参阅 ASP.NET Core 中的响应缓存。
如果不使用 [ResponseCache]
属性,响应缓存可以随 VaryByQueryKeys
而变化。 直接从 HttpContext.Features 使用 ResponseCachingFeature:
var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();
if (responseCachingFeature != null)
{
responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}
在 VaryByQueryKeys
中使用等于 *
的单个值会根据所有请求查询参数改变缓存。
响应缓存中间件使用的 HTTP 头
下表提供了有关影响响应缓存的 HTTP 头的信息。
Header | 详细信息 |
---|---|
Authorization |
如果标头存在,则不会缓存响应。 |
Cache-Control |
中间件仅考虑缓存通过 public 缓存指令标记的响应。 使用以下参数的控制缓存:
max-stale 指定任何限制,则中间件不执行任何操作。‡ proxy-revalidate 与 must-revalidate 具有相同的作用。有关详细信息,请参阅 RFC 9111:请求指令。 |
Pragma |
请求中的 Pragma: no-cache 标头具有与 Cache-Control: no-cache 相同的作用。 此标头由 Cache-Control 标头中的相关指令(若存在)覆盖。 考虑提供与 HTTP/1.0 的向后兼容性。 |
Set-Cookie |
如果标头存在,则不会缓存响应。 在请求处理管道中,任何设置了一个或多个 Cookie 的中间件都会阻止响应缓存中间件缓存响应(例如,基于 cookie 的 TempData 提供程序)。 |
Vary |
Vary 标头用于根据另一个标头更改缓存的响应。 例如,通过包含 Vary: Accept-Encoding 标头基于编码缓存响应,该标头分别缓存针对带有 Accept-Encoding: gzip 和 Accept-Encoding: text/plain 标头的请求的响应。 永远不会存储标头值为 * 的响应。 |
Expires |
不会存储或检索此标头认为过时的响应,除非被其他 Cache-Control 标头覆盖。 |
If-None-Match |
如果值不是 * 并且响应的 ETag 与提供的任何值都不匹配,则从缓存中提供完整响应。 否则,会提供 304 (Not Modified) 响应。 |
If-Modified-Since |
如果 If-None-Match 标头不存在,则当缓存的响应日期晚于提供的值时,将从缓存中提供完整响应。 否则,会提供 304 - Not Modified 响应。 |
Date |
从缓存提供服务时,如果原始响应中未提供 Date 标头,则中间件会设置该标头。 |
Content-Length |
从缓存提供服务时,如果原始响应中未提供 Content-Length 标头,则中间件会设置该标头。 |
Age |
会忽略原始响应中发送的 Age 标头。 中间件在提供缓存的响应时会计算一个新值。 |
缓存遵循请求 Cache-Control 指令
中间件遵循 RFC 9111:HTTP 缓存(第 5.2 节“缓存控制”)的规则。 这些规则要求缓存优先处理客户端发送的有效 Cache-Control
标头。 根据规范,客户端可以使用 no-cache
标头值发出请求,并强制服务器针对每个请求生成新的响应。 目前,使用中间件时,开发人员无法控制这种缓存行为,因为中间件遵循官方缓存规范。
若要了解如何进一步控制缓存行为,请探索 ASP.NET Core 的其他缓存功能。 请参阅下列主题:
疑难解答
响应缓存中间件使用 IMemoryCache,其容量有限。 超过容量时,会压缩内存缓存。
如果缓存行为与预期不一样,请确认响应是可缓存的,并且能够从缓存中提供。 检查请求的传入标头和响应的传出标头。 启用日志记录以帮助进行调试。
在测试缓存行为并对其进行故障排除时,浏览器通常会设置阻止缓存的请求头。 例如,浏览器可以在刷新页面时将 Cache-Control
标头设置为 no-cache
或 max-age=0
。 Fiddler 和其他工具可显式设置请求头并作为用于测试缓存的首选项。
缓存条件
- 请求必须生成带有 200 (OK) 状态代码的服务器响应。
- 请求方法必须是 GET 或 HEAD。
- 响应缓存中间件必须放置在需要缓存的中间件之前。 有关详细信息,请参阅 ASP.NET Core 中间件。
- 不能出现
Authorization
标头。 Cache-Control
标头参数必须是有效的,并且必须将响应标记为public
而不是private
。- 如果
Cache-Control
不存在,则不能出现Pragma: no-cache
标头,因为Cache-Control
标头在存在时会覆盖Pragma
标头。 - 不能出现
Set-Cookie
标头。 Vary
标头参数必须有效且不等于*
。Content-Length
标头值(若已设置)必须与响应正文的大小匹配。- 不使用 IHttpSendFileFeature。
- 根据
Expires
标头与max-age
和s-maxage
缓存指令所指定,响应不能过时。 - 响应缓冲必须成功。 响应的大小必须小于配置的或默认的 SizeLimit。 响应的正文大小必须小于配置的或默认的 MaximumBodySize。
- 响应必须可根据 RFC 9111:HTTP 缓存进行缓存。 例如,
no-store
指令不能出现在请求头或响应头字段中。 有关详细信息,请参阅 RFC 9111:HTTP 缓存(第 3 节“在缓存中存储响应”)。
注意
用于生成安全令牌以阻止跨网站请求伪造 (CSRF) 攻击的防伪系统将 Cache-Control
和 Pragma
标头设置为 no-cache
,这样就不会缓存响应。 若要详细了解如何禁用 HTML 窗体元素的防伪令牌,请参阅在 ASP.NET Core 中阻止跨网站请求伪造 (XSRF/CSRF) 攻击。
其他资源
本文介绍如何在 ASP.NET Core 应用中配置响应缓存中间件。 中间件确定何时可缓存响应、存储响应并提供来自缓存的响应。 有关 HTTP 缓存和 [ResponseCache]
属性的简介,请参阅响应缓存。
配置
通过共享框架将响应缓存中间件隐式提供给 ASP.NET Core 应用。
在 Startup.ConfigureServices
中,将响应缓存中间件添加到服务集合:
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCaching();
services.AddRazorPages();
}
将应用配置为将中间件与 UseResponseCaching 扩展方法配合使用,这样会将中间件添加到 Startup.Configure
中的请求处理管道:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
// UseCors must be called before UseResponseCaching
// app.UseCors("myAllowSpecificOrigins");
app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
警告
使用 CORS 中间件时,必须在 UseResponseCaching 之前调用 UseCors。
示例应用添加标头来控制对后续请求的缓存:
- Cache-Control:缓存可缓存响应长达10秒。
- Vary:将中间件配置为仅当后续请求的 Accept-Encoding 标头与原始请求头匹配时才提供缓存的响应。
// using Microsoft.AspNetCore.Http;
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };
await next();
});
前面的标头不会写入响应,并当控制器、操作或 Razor 页面存在以下情况时被覆盖:
- 具有 [ResponseCache] 属性。 即使未设置属性,情况也是如此。 例如,省略 VaryByHeader 属性将导致从响应中删除相应的标头。
响应缓存中间件仅缓存导致 200 (OK) 状态代码的服务器响应。 中间件忽略包含错误页在内的任何其他响应。
警告
包含经过身份验证的客户端内容的响应必须标记为不可缓存,以防中间件存储和提供这些响应。 有关中间件如何确定响应是否可缓存的详细信息,请参阅缓存条件。
选项
下表中显示了响应缓存选项。
选项 | 说明 |
---|---|
MaximumBodySize | 响应正文的最大可缓存大小(以字节为单位)。 默认值为 64 * 1024 * 1024 (64 MB)。 |
SizeLimit | 响应缓存中间件的大小限制(以字节为单位)。 默认值为 100 * 1024 * 1024 (100 MB)。 |
UseCaseSensitivePaths | 确定是否将响应缓存在区分大小写的路径上。 默认值为 false 。 |
下面的示例将中间件配置为:
- 缓存正文大小小于或等于 1,024 字节的响应。
- 按区分大小写的路径存储响应。 例如,分别存储
/page1
和/Page1
。
services.AddResponseCaching(options =>
{
options.MaximumBodySize = 1024;
options.UseCaseSensitivePaths = true;
});
VaryByQueryKeys
使用 MVC、Web API 控制器或 Razor Pages 页面模型时,[ResponseCache]
属性指定为响应缓存设置适当标头所需的参数。 [ResponseCache]
属性中要求必须具有中间件的唯一参数是 VaryByQueryKeys,该参数不对应实际的 HTTP 头。 有关详细信息,请参阅 ASP.NET Core 中的响应缓存。
如果不使用 [ResponseCache]
属性,响应缓存可以随 VaryByQueryKeys
而变化。 直接从 HttpContext.Features 使用 ResponseCachingFeature:
var responseCachingFeature = context.HttpContext.Features.Get<IResponseCachingFeature>();
if (responseCachingFeature != null)
{
responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}
在 VaryByQueryKeys
中使用等于 *
的单个值会根据所有请求查询参数改变缓存。
响应缓存中间件使用的 HTTP 头
下表提供了有关影响响应缓存的 HTTP 头的信息。
Header | 详细信息 |
---|---|
Authorization |
如果标头存在,则不会缓存响应。 |
Cache-Control |
中间件仅考虑缓存通过 public 缓存指令标记的响应。 使用以下参数的控制缓存:
max-stale 指定任何限制,则中间件不执行任何操作。‡ proxy-revalidate 与 must-revalidate 具有相同的作用。有关详细信息,请参阅 RFC 9111:请求指令。 |
Pragma |
请求中的 Pragma: no-cache 标头具有与 Cache-Control: no-cache 相同的作用。 此标头由 Cache-Control 标头中的相关指令(若存在)覆盖。 考虑提供与 HTTP/1.0 的向后兼容性。 |
Set-Cookie |
如果标头存在,则不会缓存响应。 在请求处理管道中,任何设置了一个或多个 Cookie 的中间件都会阻止响应缓存中间件缓存响应(例如,基于 cookie 的 TempData 提供程序)。 |
Vary |
Vary 标头用于根据另一个标头更改缓存的响应。 例如,通过包含 Vary: Accept-Encoding 标头基于编码缓存响应,该标头分别缓存针对带有 Accept-Encoding: gzip 和 Accept-Encoding: text/plain 标头的请求的响应。 永远不会存储标头值为 * 的响应。 |
Expires |
不会存储或检索此标头认为过时的响应,除非被其他 Cache-Control 标头覆盖。 |
If-None-Match |
如果值不是 * 并且响应的 ETag 与提供的任何值都不匹配,则从缓存中提供完整响应。 否则,会提供 304 (Not Modified) 响应。 |
If-Modified-Since |
如果 If-None-Match 标头不存在,则当缓存的响应日期晚于提供的值时,将从缓存中提供完整响应。 否则,会提供 304 - Not Modified 响应。 |
Date |
从缓存提供服务时,如果原始响应中未提供 Date 标头,则中间件会设置该标头。 |
Content-Length |
从缓存提供服务时,如果原始响应中未提供 Content-Length 标头,则中间件会设置该标头。 |
Age |
会忽略原始响应中发送的 Age 标头。 中间件在提供缓存的响应时会计算一个新值。 |
缓存遵循请求 Cache-Control 指令
中间件遵循 RFC 9111:HTTP 缓存(第 5.2 节“缓存控制”)的规则。 这些规则要求缓存优先处理客户端发送的有效 Cache-Control
标头。 根据规范,客户端可以使用 no-cache
标头值发出请求,并强制服务器针对每个请求生成新的响应。 目前,使用中间件时,开发人员无法控制这种缓存行为,因为中间件遵循官方缓存规范。
若要了解如何进一步控制缓存行为,请探索 ASP.NET Core 的其他缓存功能。 请参阅下列主题:
疑难解答
如果缓存行为与预期不一样,请确认响应是可缓存的,并且能够从缓存中提供。 检查请求的传入标头和响应的传出标头。 启用日志记录以帮助进行调试。
在测试缓存行为并对其进行故障排除时,浏览器可能设置以不适当的方式影响缓存的请求头。 例如,浏览器可以在刷新页面时将 Cache-Control
标头设置为 no-cache
或 max-age=0
。 以下工具可以显式设置请求头,并首选用于测试缓存:
缓存条件
- 请求必须生成带有 200 (OK) 状态代码的服务器响应。
- 请求方法必须是 GET 或 HEAD。
- 在
Startup.Configure
中,响应缓存中间件必须放置在需要缓存的中间件之前。 有关详细信息,请参阅 ASP.NET Core 中间件。 - 不能出现
Authorization
标头。 Cache-Control
标头参数必须是有效的,并且必须将响应标记为public
而不是private
。- 如果
Cache-Control
不存在,则不能出现Pragma: no-cache
标头,因为Cache-Control
标头在存在时会覆盖Pragma
标头。 - 不能出现
Set-Cookie
标头。 Vary
标头参数必须有效且不等于*
。Content-Length
标头值(若已设置)必须与响应正文的大小匹配。- 不使用 IHttpSendFileFeature。
- 根据
Expires
标头与max-age
和s-maxage
缓存指令所指定,响应不能过时。 - 响应缓冲必须成功。 响应的大小必须小于配置的或默认的 SizeLimit。 响应的正文大小必须小于配置的或默认的 MaximumBodySize。
- 响应必须可根据 RFC 9111:HTTP 缓存进行缓存。 例如,
no-store
指令不能出现在请求头或响应头字段中。 有关详细信息,请参阅 RFC 9111:HTTP 缓存(第 3 节“在缓存中存储响应”)。
注意
用于生成安全令牌以阻止跨网站请求伪造 (CSRF) 攻击的防伪系统将 Cache-Control
和 Pragma
标头设置为 no-cache
,这样就不会缓存响应。 若要详细了解如何禁用 HTML 窗体元素的防伪令牌,请参阅在 ASP.NET Core 中阻止跨网站请求伪造 (XSRF/CSRF) 攻击。