ASP.NET Core 中的响应缓存中间件

作者:John LuoRick 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) 状态代码的服务器响应。 中间件忽略包含错误页在内的任何其他响应。

警告

包含经过身份验证的客户端内容的响应必须标记为不可缓存,以防中间件存储和提供这些响应。 有关中间件如何确定响应是否可缓存的详细信息,请参阅缓存条件

前面的代码通常不会将缓存的值返回到浏览器。 使用 FiddlerPostman 或可显式设置请求头并作为用于测试缓存的首选项的其他工具。 有关详细信息,请参阅本文中的故障排除

选项

下表中显示了响应缓存选项。

选项 说明
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-age
  • max-stale†
  • min-fresh
  • must-revalidate
  • no-cache
  • no-store
  • only-if-cached
  • private
  • public
  • s-maxage
  • proxy-revalidate‡
†如果未对 max-stale 指定任何限制,则中间件不执行任何操作。
proxy-revalidatemust-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: gzipAccept-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-cachemax-age=0FiddlerPostman 和其他工具可显式设置请求头并作为用于测试缓存的首选项。

缓存条件

  • 请求必须生成带有 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-ages-maxage 缓存指令所指定,响应不能过时。
  • 响应缓冲必须成功。 响应的大小必须小于配置的或默认的 SizeLimit。 响应的正文大小必须小于配置的或默认的 MaximumBodySize
  • 响应必须可根据 RFC 9111:HTTP 缓存进行缓存。 例如,no-store 指令不能出现在请求头或响应头字段中。 有关详细信息,请参阅 RFC 9111:HTTP 缓存(第 3 节“在缓存中存储响应”)。

注意

用于生成安全令牌以阻止跨网站请求伪造 (CSRF) 攻击的防伪系统将 Cache-ControlPragma 标头设置为 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-age
  • max-stale†
  • min-fresh
  • must-revalidate
  • no-cache
  • no-store
  • only-if-cached
  • private
  • public
  • s-maxage
  • proxy-revalidate‡
†如果未对 max-stale 指定任何限制,则中间件不执行任何操作。
proxy-revalidatemust-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: gzipAccept-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-cachemax-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-ages-maxage 缓存指令所指定,响应不能过时。
  • 响应缓冲必须成功。 响应的大小必须小于配置的或默认的 SizeLimit。 响应的正文大小必须小于配置的或默认的 MaximumBodySize
  • 响应必须可根据 RFC 9111:HTTP 缓存进行缓存。 例如,no-store 指令不能出现在请求头或响应头字段中。 有关详细信息,请参阅 RFC 9111:HTTP 缓存(第 3 节“在缓存中存储响应”)。

注意

用于生成安全令牌以阻止跨网站请求伪造 (CSRF) 攻击的防伪系统将 Cache-ControlPragma 标头设置为 no-cache,这样就不会缓存响应。 若要详细了解如何禁用 HTML 窗体元素的防伪令牌,请参阅在 ASP.NET Core 中阻止跨网站请求伪造 (XSRF/CSRF) 攻击

其他资源