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

使用新版CacheControl属性缓存HttpResult/MemoryStream的问题

解决ServiceStack中CacheResponse对PDF流响应不生效的问题

你遇到的问题很典型:ServiceStack的CacheResponse属性默认是为DTO对象设计的缓存机制,当返回HttpResult(尤其是包含流的PDF响应)时,默认的缓存拦截逻辑不会自动处理这类响应,所以你看不到缓存记录。

原因分析

ServiceStack的默认缓存系统会序列化返回的DTO并存储到缓存中,但对于HttpResult这种直接封装原始响应内容(比如MemoryStream)的类型,框架无法自动识别并序列化缓存流数据,因此需要我们手动介入缓存逻辑。

解决方案:手动实现流响应的缓存

我们可以借助ServiceStack内置的ICacheClient来手动处理PDF流的缓存,步骤如下:

  1. 生成唯一缓存键:基于请求参数生成唯一的缓存键,确保不同请求参数对应不同的缓存条目。
  2. 检查缓存是否存在:如果缓存中有对应的PDF流,直接返回;否则生成流并缓存。
  3. 缓存流内容:注意要复制一份流进行缓存(避免原流读取后位置变化),同时设置缓存过期时间。
  4. 设置响应缓存头:返回时添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:52:52