You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7如何实现按ID自定义Output Cache策略?

问题描述

我正在基于.NET 7实现新特性,已经配置了基础输出缓存,用标签缓存请求列表的机制运行正常。现在想为按ID查询的单个请求实现独立缓存,要求只有当该请求详情被PUT/PATCH更新时,才清除对应ID的缓存,而不是清空全部缓存。我找到了IOutputCachePolicy接口,但官方文档没有实现指引,也找不到DefaultOutputCachePolicy的源码,请问该怎么实现这个接口?

当前启动配置代码:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();
builder.Services.AddCors();
builder.Services.AddOutputCache();
//builder.Services.InitializeApplication(builder.Configuration);

var app = builder.Build();

app.UseOutputCache();
app.UseCors();
//app.UseAuthentication();
//app.UseAuthorization();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGroup("reports-api/requests")
    .MapRequestsApi();

app.MapHealthChecks("/healthcheck");

app.Run();

路由组代码:

public static class RequestsEndpoint
{
    public static RouteGroupBuilder MapRequestsApi(this RouteGroupBuilder group)
    {
        group.MapGet("/all", async (IMediator mediator) =>
            {
                await Task.Delay(2000);
                return await mediator.Send(new RetrieveRequestsQuery());
            })
            .CacheOutput(x => x.Tag("requests"));

        group.MapPost("/add", async (string details, IMediator mediator, IOutputCacheStore store) =>
        { 
            await mediator.Send(new AddRequestCommand { Details = details });
            await store.EvictByTagAsync("requests", CancellationToken.None);

        });

        group.MapGet("/{id}", async (Guid requestId, IMediator mediator) =>
        {
            await mediator.Send(new RetrieveRequestDetailsQuery()
            {
                Id = requestId
            });
        });

        //group.MapPut("/{id}", UpdateRequest);
        //group.MapDelete("/{id}", DeleteRequest);

        return group;
    }
}

待实现的接口代码:

public class ByIdCachePolicy : IOutputCachePolicy
{
    public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();

    public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();

    public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation) => throw new NotImplementedException();
}

解决方案

其实无需自定义IOutputCachePolicy,利用.NET 7内置的标签缓存扩展就能实现需求;如果一定要自定义策略,也可以基于默认逻辑扩展。

方式一:直接用动态标签实现(推荐)

1. 为单个请求GET接口添加动态ID标签

修改MapGet("/{id}")的缓存配置,用路由参数生成唯一标签:

group.MapGet("/{id}", async (Guid requestId, IMediator mediator) =>
{
    var result = await mediator.Send(new RetrieveRequestDetailsQuery()
    {
        Id = requestId
    });
    return Results.Ok(result);
})
.CacheOutput(x => x.Tag($"request-{requestId}")); // 每个ID对应独立标签

2. 更新接口清除对应ID的缓存

实现PUT/PATCH接口时,注入IOutputCacheStore并精准清除目标标签:

group.MapPut("/{id}", async (Guid id, UpdateRequestCommand command, IMediator mediator, IOutputCacheStore store) =>
{
    command.Id = id;
    await mediator.Send(command);
    // 清除当前ID的缓存标签
    await store.EvictByTagAsync($"request-{id}", CancellationToken.None);
    // 可选:同时更新列表缓存(如果需要)
    await store.EvictByTagAsync("requests", CancellationToken.None);
    return Results.NoContent();
});

方式二:自定义IOutputCachePolicy实现

如果需要更复杂的缓存规则,可以基于默认策略扩展:

public class ByIdCachePolicy : IOutputCachePolicy
{
    private readonly DefaultOutputCachePolicy _defaultPolicy = new();

    public async ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation)
    {
        // 先执行默认策略的缓存判定逻辑
        await _defaultPolicy.CacheRequestAsync(context, cancellation);
        
        // 对单个请求的GET接口,添加动态ID标签
        if (context.HttpContext.Request.Method == HttpMethods.Get &&
            context.HttpContext.Request.RouteValues.TryGetValue("id", out var idValue) &&
            idValue is Guid requestId)
        {
            context.Tags.Add($"request-{requestId}");
        }
    }

    public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation)
    {
        // 复用默认策略的缓存读取逻辑
        return _defaultPolicy.ServeFromCacheAsync(context, cancellation);
    }

    public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation)
    {
        // 复用默认策略的响应处理逻辑
        return _defaultPolicy.ServeResponseAsync(context, cancellation);
    }
}

注册并使用自定义策略

在Program.cs中注册策略:

builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("ByIdCachePolicy", policy => policy.UsePolicy<ByIdCachePolicy>());
});

在路由中应用:

group.MapGet("/{id}", async (Guid requestId, IMediator mediator) =>
{
    var result = await mediator.Send(new RetrieveRequestDetailsQuery()
    {
        Id = requestId
    });
    return Results.Ok(result);
})
.CacheOutput(x => x.PolicyName("ByIdCachePolicy"));

内容的提问来源于stack exchange,提问作者Dragos Stoica

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 04:35:28