使用新版CacheControl属性缓存HttpResult/MemoryStream的问题
解决ServiceStack中CacheResponse对PDF流响应不生效的问题
你遇到的问题很典型:ServiceStack的CacheResponse属性默认是为DTO对象设计的缓存机制,当返回HttpResult(尤其是包含流的PDF响应)时,默认的缓存拦截逻辑不会自动处理这类响应,所以你看不到缓存记录。
原因分析
ServiceStack的默认缓存系统会序列化返回的DTO并存储到缓存中,但对于HttpResult这种直接封装原始响应内容(比如MemoryStream)的类型,框架无法自动识别并序列化缓存流数据,因此需要我们手动介入缓存逻辑。
解决方案:手动实现流响应的缓存
我们可以借助ServiceStack内置的ICacheClient来手动处理PDF流的缓存,步骤如下:
- 生成唯一缓存键:基于请求参数生成唯一的缓存键,确保不同请求参数对应不同的缓存条目。
- 检查缓存是否存在:如果缓存中有对应的PDF流,直接返回;否则生成流并缓存。
- 缓存流内容:注意要复制一份流进行缓存(避免原流读取后位置变化),同时设置缓存过期时间。
- 设置响应缓存头:返回时添加
Cache-Control头,让客户端也遵循缓存策略。
修改后的代码示例:
[Authenticate] public class AdvReportPDFService : Service { public object Get(AdvRptPitchPercentages request) { // 基于请求生成唯一缓存键(可根据实际需求调整键的生成逻辑) var cacheKey = $"AdvRptPitchPercentages_{request.ToJsv()}"; var cachedPdfStream = Cache.Get<MemoryStream>(cacheKey); if (cachedPdfStream != null) { cachedPdfStream.Position = 0; return new HttpResult(cachedPdfStream, "application/pdf") { Headers = { { HttpHeaders.CacheControl, $"public, max-age={CacheExpirySeconds.TwentyFourHours}" } } }; } // 生成PDF流 var pdfStream = SomeFunctionThatReturnsAMemoryStream(); pdfStream.Position = 0; // 复制流到新的MemoryStream用于缓存(避免原流被读取后位置改变) var streamToCache = new MemoryStream(); pdfStream.CopyTo(streamToCache); streamToCache.Position = 0; // 缓存流,设置过期时间 Cache.Set(cacheKey, streamToCache, TimeSpan.FromSeconds(CacheExpirySeconds.TwentyFourHours)); // 重置原流位置,返回响应 pdfStream.Position = 0; return new HttpResult(pdfStream, "application/pdf") { Headers = { { HttpHeaders.CacheControl, $"public, max-age={CacheExpirySeconds.TwentyFourHours}" } } }; } }
额外注意事项
- 缓存键的生成:上面用
request.ToJsv()将请求序列化为字符串作为键的一部分,确保不同参数生成不同键,你也可以根据请求的关键参数自定义键的生成逻辑。 - 流的可复用性:缓存流时一定要复制一份,因为流的
Position属性是状态性的,原流被读取后位置会移动,直接缓存原流会导致后续读取缓存时内容为空。 - 缓存客户端配置:确保你的AppHost已经配置了缓存客户端(比如
MemoryCacheClient或Redis缓存),如果POCO能正常缓存,这一步应该已经完成了。
内容的提问来源于stack exchange,提问作者taglius
相关产品推荐
相关产品推荐

