ASP.NET Core 3.1如何实现服务端全响应缓存及缓存失效?
解决方案:ASP.NET Core 3.1 服务器端页面缓存(支持手动失效)
刚好我之前帮朋友解决过类似的迁移问题,你提到的需求——在服务器内存中缓存完整响应、设置30天过期、支持手动清除缓存,确实没法靠[ResponseCache]属性或默认的<cache>标签助手直接实现,我给你整理一套落地的方案:
先理清现有方案的局限性
[ResponseCache(Duration = 30)]:本质是给客户端发缓存头,让浏览器/代理去缓存内容,服务器每次还是会重新生成响应,完全达不到减少服务器计算量的目的。<cache>标签助手:虽然能做服务器端缓存,但默认配置下没法灵活实现全局手动失效指定缓存项,而且长周期(30天)的缓存配置也不够友好。
具体实现步骤
1. 自定义服务器端页面缓存过滤器
我们自己写一个Action过滤器,拦截请求和响应:先查缓存,有缓存直接返回;没缓存就执行页面逻辑,把生成的响应存入内存缓存。
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Caching.Memory; using System.IO; using System.Text; using System.Threading.Tasks; public class ServerSidePageCacheAttribute : ActionFilterAttribute { private readonly int _cacheDurationDays; private const string _cacheKeyPrefix = "PageCache_"; public ServerSidePageCacheAttribute(int cacheDurationDays) { _cacheDurationDays = cacheDurationDays; } public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { // 生成唯一缓存键:路由+查询参数,确保不同请求的缓存独立 var cacheKey = $"{_cacheKeyPrefix}{context.HttpContext.Request.Path}{context.HttpContext.Request.QueryString}"; var memoryCache = context.HttpContext.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache; // 命中缓存直接返回 if (memoryCache.TryGetValue(cacheKey, out string cachedContent)) { context.HttpContext.Response.ContentType = "text/html"; await context.HttpContext.Response.WriteAsync(cachedContent, Encoding.UTF8); return; } // 拦截响应流,保存页面输出 var originalResponseStream = context.HttpContext.Response.Body; using var memoryStream = new MemoryStream(); context.HttpContext.Response.Body = memoryStream; // 执行页面逻辑,生成原始响应 await next(); // 将响应内容存入内存缓存,设置30天过期 memoryStream.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(memoryStream); var content = await reader.ReadToEndAsync(); var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(System.TimeSpan.FromDays(_cacheDurationDays)); memoryCache.Set(cacheKey, content, cacheOptions); // 把缓存的内容写回原始响应流,正常返回给客户端 memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalResponseStream); context.HttpContext.Response.Body = originalResponseStream; } }
2. 在Razor Page上应用缓存过滤器
直接在你的IndexModel上添加自定义属性,指定30天缓存:
[ServerSidePageCache(30)] public class IndexModel : PageModel { private readonly ILogger<IndexModel> _logger; public IndexModel(ILogger<IndexModel> logger) { _logger = logger; } public void OnGet() { } }
3. 实现手动清除缓存的功能
写一个缓存失效服务,用来在后台修改内容后,精准清除对应页面的缓存:
using Microsoft.Extensions.Caching.Memory; public class CacheInvalidationService { private readonly IMemoryCache _memoryCache; private const string _cacheKeyPrefix = "PageCache_"; public CacheInvalidationService(IMemoryCache memoryCache) { _memoryCache = memoryCache; } // 根据页面路径+查询参数清除指定缓存 public void InvalidatePageCache(string pagePath, string queryString = "") { var cacheKey = $"{_cacheKeyPrefix}{pagePath}{queryString}"; _memoryCache.Remove(cacheKey); } // 可选:清除所有页面缓存 // 注意:IMemoryCache没有直接批量清除的方法,需要额外维护缓存键集合 // 比如添加缓存时把键存入ConcurrentDictionary,这里遍历移除即可 }
然后在Startup.cs里注册这个服务:
public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); services.AddScoped<CacheInvalidationService>(); // 其他服务注册... }
之后你就可以在后台管理页面或者API接口里,注入CacheInvalidationService,调用InvalidatePageCache("/")来清除首页的缓存了。
额外补充
- 缓存键定制:如果需要个性化缓存(比如不同用户看到不同内容),可以在缓存键里加入用户ID等标识。
- 多服务器场景:如果你的应用是集群部署,单机内存缓存就不够用了,把
IMemoryCache换成IDistributedCache(比如Redis),逻辑基本一致。 - 内存占用优化:大页面缓存会占用较多内存,可以根据实际情况调整缓存过期策略,或者限制缓存的总大小。
内容的提问来源于stack exchange,提问作者Alireza Noori
相关产品推荐
相关产品推荐

