.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
相关产品推荐
相关产品推荐

