ASP.NET Core 输出缓存策略:为接口设置 10 分钟过期时间的实战示例(practical-aspnetcore 的 output-cache-8)

原创2026-10-14 12:51:141,850 阅读
文章标签:示例工程

ASP.NET Core 输出缓存策略:为接口设置 10 分钟过期时间的实战示例(practical-aspnetcore 的 output-cache-8)

本文基于 practical-aspnetcore 仓库中的 output-cache-8 示例 展开。该示例演示了如何在 ASP.NET Core 中注册一个指定过期时间为 10 分钟的输出缓存策略(Output Cache Policy),并在单个端点(endpoint)上显式应用它。读完本文,你将掌握 AddOutputCache、AddPolicy、Expire、CacheOutput 等核心 API 的用法,理解“命名策略覆盖全局基础策略”的机制,并能在自己的 Minimal API 项目中复现同样的缓存行为。

输出缓存与缓存策略:先理解两个核心概念

ASP.NET Core 自 .NET 7 起提供了输出缓存中间件(Output Caching Middleware),它允许将 HTTP 响应整体缓存起来,后续相同请求直接复用缓存响应,避免重复执行处理器逻辑、显著降低服务器开销。output-cache-8 所在的 output-cache-middleware 系列 共有 8 个示例,循序渐进地覆盖了该中间件的主要用法:

示例 主题
output-cache-1 使用最基本的 AddOutputCache() 选项
output-cache-2 按一个或多个查询字符串参数区分缓存
output-cache-3 为缓存项打标签,并通过 IOutputCacheStore.EvictByTagAsync 按标签清除
output-cache-4 为所有端点启用输出缓存
output-cache-5 全局策略:缓存带 "cache" 查询字符串参数的请求
output-cache-6 全局策略:缓存 URL 段以 "/cache" 开头的请求
output-cache-7 定义并应用命名策略,具体策略覆盖全局基础策略
output-cache-8 定义过期时间为 10 分钟的缓存策略

本示例聚焦“策略(Policy)”与“过期时间(Expiration)”的组合用法。在输出缓存体系中,策略分为两类:

  • 基础策略(Base Policy):通过 AddBasePolicy 注册,定义一个匹配条件(谓词),命中条件的请求默认都会走该策略;
  • 命名策略(Named Policy):通过 AddPolicy("名称", ...) 注册,仅在端点显式调用 CacheOutput("名称") 时才生效,且优先于基础策略。

output-cache-8 同时演示了这两者的搭配:既有一个面向 /cached 路径的基础策略,又有一个名为 "10Minutes"、过期时间为 10 分钟的命名策略。

示例概览:三个端点对照演示

示例首页通过一个 HTML 页面列出三个链接,用 DateTime.UtcNow() 的输出时间直观展示缓存效果:

  • /now:不缓存,每次刷新都返回新的 UTC 时间;
  • /cached/now:命中 /cached 前缀的基础策略,按默认 60 秒过期缓存;
  • /cached/now-nope:路径同样以 /cached 开头,但端点显式应用了 "10Minutes" 命名策略,因此按 10 分钟过期缓存。

(“默认 60 秒”这一说法直接来自示例首页的文案 DateTime.UtcNow() cached for default 60 seconds,是仓库内可验证的事实。)

核心代码逐段解析

output-cache-8 的完整源码 Program.cs 如下:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.With(c => c.HttpContext.Request.Path.StartsWithSegments("/cached")));
    options.AddPolicy("10Minutes", o => o.Expire(TimeSpan.FromMinutes(10)));
});

var app = builder.Build();
app.MapGet("/", () =>
{
    return Results.Content("""
    <html>
        <body>
            <ul>
                <li><a href="/now">DateTime.UtcNow()</a></li>
                <li><a href="/cached/now">DateTime.UtcNow() cached for default 60 seconds</a></li>
                <li><a href="/cached/now-nope">DateTime.UtcNow() cached for 10 minutes</a></li>
            </ul>
        </body>
    </html>
    """, "text/html");
});

app.UseOutputCache();
app.MapGet("/now", () => DateTime.UtcNow.ToString());
app.MapGet("/cached/now", () => DateTime.UtcNow.ToString());
app.MapGet("/cached/now-nope", () => DateTime.UtcNow.ToString()).CacheOutput("10Minutes");

app.Run();

第一步:注册输出缓存服务并定义策略

builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.With(c => c.HttpContext.Request.Path.StartsWithSegments("/cached")));
    options.AddPolicy("10Minutes", o => o.Expire(TimeSpan.FromMinutes(10)));
});

这里发生了三件事:

  1. AddOutputCache():向依赖注入容器注册输出缓存服务(缓存存储、策略注册表、中间件所需的依赖)。项目文件 output-cache-8.csproj 表明示例基于 net10.0 目标框架,ImplicitUsings 已启用,因此无需显式 using 命名空间即可使用这些 API。
  2. AddBasePolicy:注册基础策略,谓词 c.HttpContext.Request.Path.StartsWithSegments("/cached") 表示“凡是请求路径以 /cached 段开头的端点,都默认纳入输出缓存”。这是该系列中 output-cache-6 的延续(output-cache-6 源码 使用完全相同的谓词),此处基础策略未显式指定过期时间,因此采用框架默认的 60 秒。
  3. AddPolicy("10Minutes", o => o.Expire(TimeSpan.FromMinutes(10))):注册一个名为 "10Minutes" 的命名策略,通过 Expire(TimeSpan.FromMinutes(10)) 把该策略的缓存过期时间设置为 10 分钟。Expire 接受一个 TimeSpan,是控制缓存存活时长的最直接手段。

第二步:启用中间件并挂载端点

app.UseOutputCache();
app.MapGet("/now", () => DateTime.UtcNow.ToString());
app.MapGet("/cached/now", () => DateTime.UtcNow.ToString());
app.MapGet("/cached/now-nope", () => DateTime.UtcNow.ToString()).CacheOutput("10Minutes");

关键点在于:

  • app.UseOutputCache() 必须调用,中间件才会实际介入请求管线,在端点执行前查询缓存、在端点执行后写入缓存;
  • 三个端点中,/cached/now 与 /cached/now-nope 都满足基础策略的 /cached 前缀条件,本来都会按默认 60 秒缓存;
  • 唯独 /cached/now-nope 通过 .CacheOutput("10Minutes") 显式指定了命名策略。由于命名策略优先于基础策略,这个端点最终按 10 分钟过期,而 /cached/now 仍按基础策略的默认 60 秒过期——首页文案中的“cached for 10 minutes”与“cached for default 60 seconds”正是对这一行为的实证。

第三步:项目配置

output-cache-8.csproj 内容极简:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>true</ImplicitUsings>
  </PropertyGroup>
</Project>

值得注意的是,整个示例不需要引入任何 NuGet 包——输出缓存是 ASP.NET Core 框架自带能力,这是它相比 Response Caching 等方案的便利之处。

运行与验证

在示例目录下执行:

dotnet run

随后访问 http://localhost:5000/(具体端口以启动日志为准)打开演示首页,按顺序观察:

  1. 点击 /now,多次刷新,时间每次都变化——未缓存;
  2. 点击 /cached/now,多次刷新,时间在 60 秒内保持不变——基础策略默认 60 秒缓存生效;
  3. 点击 /cached/now-nope,多次刷新,时间在 10 分钟内保持不变——"10Minutes" 命名策略覆盖了基础策略。

通过这个三端点对照,可以直观地验证“策略是端点级显式行为”这一核心机制:基础策略负责兜底,命名策略负责精准控制。

为什么命名策略能覆盖基础策略:对照 output-cache-7

output-cache-8 与 output-cache-7 示例 的代码结构几乎完全一致,后者在其 Readme 中明确写道:"Specific policy override the global base policy."(具体策略覆盖全局基础策略)。

对比两份 Program.cs 可以看到同一模式的两种变体:

  • output-cache-7 注册 AddPolicy("NoCache", o => o.NoCache()),并在端点 CacheOutput("NoCache"),使该端点完全不缓存;
  • output-cache-8 注册 AddPolicy("10Minutes", o => o.Expire(TimeSpan.FromMinutes(10))),并在端点 CacheOutput("10Minutes"),使该端点缓存 10 分钟。

两者用的是同一条规则:当端点显式指定命名策略时,命名策略的配置(缓存与否、过期时长)会覆盖全局基础策略对该端点的约束。output-cache-8 正是“控制过期时间”这一维度的演示。从源码结构看,CacheOutput(policyName) 是 OutputCachePolicyBuilder 应用策略的入口,它会以该命名策略的定义为准构建端点的缓存行为,因此同一路径前缀下的端点可以拥有截然不同的过期时间。

同系列扩展:从 60 秒到 10 分钟再到更多玩法

本示例的 10 分钟过期策略并非孤立存在,它在整个系列中处于“策略体系”的收尾位置。若想进一步掌握输出缓存,可以顺着下列源码继续深入:

  • output-cache-1:不配置任何策略,端点直接 .CacheOutput(),使用默认 60 秒过期——理解最小可用形态;
  • output-cache-2:.CacheOutput(p => p.SetVaryByQuery("version", "culture")),按查询字符串参数拆分缓存条目;
  • output-cache-3:.CacheOutput(p => p.Tag("datetime")) 打标签,再通过 IOutputCacheStore.EvictByTagAsync 主动清除整组缓存;
  • output-cache-4:AddBasePolicy(builder => builder.With(c => true)),让所有端点默认都缓存;
  • output-cache-5:谓词判断查询字符串是否包含 "cache" 字样,实现“按条件缓存”;
  • output-cache-6:谓词判断路径是否以 /cached 开头,正是本示例基础策略的出处;
  • output-cache-7:NoCache() 策略覆盖基础策略,与本示例互为镜像。

通过这组对照可以看出,OutputCachePolicyBuilder 提供的 Expire、NoCache、SetVaryByQuery、Tag 等 API 组合起来,足以覆盖绝大多数 Web API 的缓存需求:默认兜底、按需差异化、可控过期、可主动失效。

小结

output-cache-8 用不到 30 行代码讲清了 ASP.NET Core 输出缓存中“策略 + 过期时间”的完整链路:用 AddBasePolicy 建立默认缓存范围,用 AddPolicy("10Minutes", o => o.Expire(TimeSpan.FromMinutes(10))) 定义带 10 分钟过期时间的命名策略,再用端点上的 CacheOutput("10Minutes") 让特定接口获得更长的缓存寿命。将这一模式推广开来,你只需调整 TimeSpan 参数,就能为不同接口定制从秒级到小时级的差异化缓存策略。

登录后查看全文
practical-aspnetcore