C# Minimal API:输出缓存

发布: (2025年12月20日 GMT+8 01:56)
4 min read
原文: Dev.to

Source: Dev.to

在服务器上存储生成的响应,并直接提供,而无需重新执行端点。
Microsoft Docs

输出缓存是一种 作为中间件实现的服务器端缓存机制。它会存储完整的 HTTP 响应,并在后续请求中直接返回,而无需再次执行端点。

  • 默认使用 内存存储,但也可以使用 Redis 等分布式存储作为后端。
  • 它最适用于在定义的时间窗口内返回相同响应的 高开销服务器端操作

工作原理

当请求到达时:

  1. 缓存命中 – 已存在缓存的响应 → 中间件短路管道并返回缓存的响应。
  2. 缓存未命中 – 没有缓存的响应 → 正常处理请求,并将响应缓存以供后续请求使用。

添加输出缓存

  1. 使用 AddOutputCache() 注册服务。
  2. 定义一个或多个策略。
  3. 将策略应用到端点。
  4. 使用 UseOutputCache() 添加中间件。
var builder = WebApplication.CreateBuilder(args);

// 1️⃣ Register output‑cache services
builder.Services.AddOutputCache(options =>
{
    // Default policy (10 s)
    options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(10)));

    // Named policy (20 s)
    options.AddPolicy("OutputCache20Seconds", policy => policy.Expire(TimeSpan.FromSeconds(20)));
});

var app = builder.Build();

// 2️⃣ Apply policies to Minimal API endpoints
app.MapGet("/default-cache-policy", () => new[] { "someresponse2" })
   .CacheOutput();                         // uses default policy

app.MapGet("/custom-cache-policy", () => new[] { "someresponse" })
   .CacheOutput("OutputCache20Seconds");   // uses named policy

// 3️⃣ Add the middleware (must be after UseCors() if you use CORS)
app.UseOutputCache();

app.Run();

注意: 在使用 CORS 中间件的应用程序中,UseOutputCache() 必须在 UseCors() 之后 调用。

带 Authorization 头的输出缓存

默认情况下,当请求中包含 Authorization 头时,输出缓存 不会缓存响应——这是为了避免将已认证的内容错误地提供给其他用户的安全措施。

要覆盖此行为,请创建自定义的输出缓存策略:

internal static class CustomOutputCachingPolicyFactory
{
    internal static CustomOutputCachingPolicy Create(TimeSpan expiration)
        => new(expiration);
}

internal sealed class CustomOutputCachingPolicy : IOutputCachePolicy
{
    private readonly TimeSpan _expiration;

    internal CustomOutputCachingPolicy(TimeSpan expiration) => _expiration = expiration;

    public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation)
    {
        var canCache = HttpMethods.IsGet(context.HttpContext.Request.Method) ||
                       HttpMethods.IsHead(context.HttpContext.Request.Method);

        context.EnableOutputCaching = canCache;
        context.AllowCacheLookup   = canCache;
        context.AllowCacheStorage  = canCache;
        context.AllowLocking       = true;
        context.ResponseExpirationTimeSpan = _expiration;
        context.CacheVaryByRules.QueryKeys = "*";

        return ValueTask.CompletedTask;
    }

    public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation)
        => ValueTask.CompletedTask;

    public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation)
    {
        var response = context.HttpContext.Response;

        // Do not cache if Set‑Cookie header is present
        if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
        {
            context.AllowCacheStorage = false;
            return ValueTask.CompletedTask;
        }

        // Only cache successful (200) or permanent redirect (301) responses
        if (response.StatusCode is not (StatusCodes.Status200OK or StatusCodes.Status301MovedPermanently))
        {
            context.AllowCacheStorage = false;
        }

        return ValueTask.CompletedTask;
    }
}

简便扩展(适用于 Minimal API)

这些扩展简化了自定义策略的注册:

public static class OutputCachingExtensions
{
    public static void AddCustomOutputCachingPolicy(
        this OutputCacheOptions options,
        params (string Name, TimeSpan Expiration)[] policies)
    {
        foreach (var (name, expiration) in policies)
        {
            options.AddPolicy(name,
                CustomOutputCachingPolicyFactory.Create(expiration));
        }
    }

    public static IServiceCollection AddOutputCacheWithCustomPolicy(
        this IServiceCollection services,
        params (string Name, TimeSpan Expiration)[] policies) =>
        services.AddOutputCache(opts => opts.AddCustomOutputCachingPolicy(policies));

    public static IServiceCollection AddOutputCacheWithCustomPolicy(
        this IServiceCollection services,
        Action<OutputCacheOptions> configure,
        params (string Name, TimeSpan Expiration)[] policies) =>
        services.AddOutputCache(opts =>
        {
            configure(opts);
            opts.AddCustomOutputCachingPolicy(policies);
        });
}

附加帮助方法:

options.AddCustomOutputCachingPolicy(policies);
configureOptions.Invoke(options);
});

public static RouteHandlerBuilder CustomCacheOutput(this RouteHandlerBuilder routeHandlerBuilder, string name)
    => routeHandlerBuilder.CacheOutput(name);

重要提示:除非响应对所有用户完全相同,否则不要在需要身份验证或针对特定用户的端点上使用输出缓存。

何时使用

当响应 生成成本高 且不经常更改时使用输出缓存:

  • 昂贵的服务器端计算
  • 高执行成本的频繁请求响应(例如,报告生成、用户资料渲染)
Back to Blog

相关文章

阅读更多 »