ASP.NET Core实现外部Web请求X分钟缓存的方案咨询
缓存外部API请求的几种实用方案
我来分享几个能帮你实现外部API请求缓存的方案,兼顾速度优化,包括你提到的DelegatingHandler实现,还有其他更灵活的方式:
方案一:自定义DelegatingHandler实现缓存
这是你正在尝试的思路,我给你完善一下具体实现,支持内存缓存或者分布式缓存:
首先创建一个缓存Handler:
public class CacheHandler : DelegatingHandler { private readonly IMemoryCache _cache; private readonly int _cacheMinutes; public CacheHandler(IMemoryCache cache, int cacheMinutes) { _cache = cache; _cacheMinutes = cacheMinutes; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 用请求的方法和URL作为缓存键,确保唯一性 var cacheKey = $"{request.Method}_{request.RequestUri}"; // 先尝试从缓存获取响应 if (_cache.TryGetValue(cacheKey, out HttpResponseMessage cachedResponse)) { // 返回缓存的响应副本,避免原响应被释放 return new HttpResponseMessage(cachedResponse.StatusCode) { Content = new StringContent(await cachedResponse.Content.ReadAsStringAsync()), Headers = cachedResponse.Headers }; } // 没有缓存就调用外部API var response = await base.SendAsync(request, cancellationToken); // 如果响应成功,缓存起来 if (response.IsSuccessStatusCode) { // 缓存响应的副本,因为原响应会被HttpClient处理释放 var responseToCache = new HttpResponseMessage(response.StatusCode) { Content = new StringContent(await response.Content.ReadAsStringAsync()), Headers = response.Headers }; _cache.Set(cacheKey, responseToCache, TimeSpan.FromMinutes(_cacheMinutes)); } return response; } }
然后在Startup/Program.cs里注册这个Handler:
builder.Services.AddHttpClient("ExternalApiClient") .AddHttpMessageHandler(sp => new CacheHandler(sp.GetRequiredService<IMemoryCache>(), 10)); // 缓存10分钟
这样所有通过这个HttpClient发送的请求都会自动走缓存逻辑,适合全局统一缓存的场景。如果是分布式部署,把IMemoryCache换成IDistributedCache即可。
方案二:使用Polly缓存策略(推荐)
Polly是.NET生态里非常流行的弹性库,缓存是它的核心功能之一,结合HttpClientFactory使用会更简洁,还能搭配重试、熔断等其他策略:
首先安装Polly和Polly.Extensions.Http包,然后注册服务:
builder.Services.AddHttpClient("ExternalApiClient") .AddPolicyHandler(Policy.CacheAsync<HttpResponseMessage>( new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())), TimeSpan.FromMinutes(10)));
如果需要更细粒度的控制(比如只缓存特定状态码的响应),可以调整策略:
var cachePolicy = Policy.CacheAsync<HttpResponseMessage>( new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions())), TimeSpan.FromMinutes(10), onCacheGetAsync: (context, key) => { // 缓存命中时的日志逻辑 return Task.CompletedTask; }, onCachePutAsync: (context, key) => { // 缓存写入时的日志逻辑 return Task.CompletedTask; }, shouldCacheResultAsync: (response, context) => { // 只缓存成功的响应 return Task.FromResult(response.IsSuccessStatusCode); }); builder.Services.AddHttpClient("ExternalApiClient") .AddPolicyHandler(cachePolicy);
这个方案的好处是不用自己写Handler,Polly已经帮你处理了缓存的细节,还能轻松扩展其他弹性策略。
方案三:在Action层面直接缓存外部API数据
如果只需要在特定Action里缓存某个字段的外部API数据,也可以直接在Action里处理,更灵活:
[ApiController] [Route("api/[controller]")] public class MyController : ControllerBase { private readonly IHttpClientFactory _httpClientFactory; private readonly IMemoryCache _cache; public MyController(IHttpClientFactory httpClientFactory, IMemoryCache cache) { _httpClientFactory = httpClientFactory; _cache = cache; } [HttpGet] public async Task<IActionResult> GetModel() { var cacheKey = "ExternalApi_Data_FieldX"; // 可以根据请求参数动态生成缓存键 if (!_cache.TryGetValue(cacheKey, out string externalData)) { var client = _httpClientFactory.CreateClient("ExternalApiClient"); var response = await client.GetAsync("/external-data"); response.EnsureSuccessStatusCode(); externalData = await response.Content.ReadAsStringAsync(); _cache.Set(cacheKey, externalData, TimeSpan.FromMinutes(10)); } // 构建模型 var model = new MyModel { ExternalField = externalData, // 其他字段... }; return Ok(model); } }
这种方式适合你只需要缓存特定请求的场景,缓存键可以根据请求参数动态生成,避免不同请求的缓存冲突。
注意事项
- 如果是分布式系统,一定要用
IDistributedCache(比如Redis)代替IMemoryCache,避免多个节点缓存不一致。 - 缓存键要确保唯一性,最好包含请求的方法、URL和关键参数。
- 可以根据外部API的缓存策略调整缓存时间,比如遵循外部API返回的
Cache-Control头。
内容的提问来源于stack exchange,提问作者zazka
相关产品推荐
相关产品推荐

