将序列化的 HttpResponseMessage 缓存到 Redis。读取错误。 “InvalidOperationException:流已被消耗。无法再次读取。”

Posted

技术标签:

【中文标题】将序列化的 HttpResponseMessage 缓存到 Redis。读取错误。 “InvalidOperationException:流已被消耗。无法再次读取。”【英文标题】:Caching Serialized HttpResponseMessage to Redis. Error on read. "InvalidOperationException: The stream was already consumed. It cannot be read again." 【发布时间】:2020-05-14 07:40:22 【问题描述】:

我有一个接受 API 请求的函数,检查结果是否存在于 Redis 缓存中,如果存在则返回缓存值,如果不存在则发送 API 请求然后缓存该值。

private async Task<HttpResponseMessage> RestGetCachedAsync(string query, ILogger logger = null)
    
        string key = $"GET:query";
        HttpResponseMessage response;

        var cacheResponse = await _cacheService.GetStringValue(key);

        if (cacheResponse != null)
        
            response = JsonConvert.DeserializeObject<HttpResponseMessage>(cacheResponse, new JsonSerializerSettings
            
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
            );

            if(response.IsSuccessStatusCode) return response;
        

        response = await RestGetAsync(query, logger);

        if (response.IsSuccessStatusCode)
        
            await _cacheService.SetStringValue(key, JsonConvert.SerializeObject(response, new JsonSerializerSettings
            
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
            ));
                    

        return response;
    

读取之前查询过的 API 时

public async Task<string> DeserializeHttpResponse(HttpResponseMessage response)
    
        return await response.Content.ReadAsStringAsync();
    

我收到以下错误。

InvalidOperationException: The stream was already consumed. It cannot be read again.

【问题讨论】:

我没有运气序列化和反序列化HttpResponseMessage。所以我最终使用了similar to this的方法。 看起来 HttpResponseMessage.Content 数据没有与父对象一起存储,只能检索一次...所以通过读取然后在检索时序列化 HttpResponseMessage,反序列化并再次读取价值已经丢失。希望我可以以本机格式缓存,但看起来好像不需要按照您的建议先打开包装。 【参考方案1】:

在 cmets 对这个问题进行讨论后,我意识到我的错。我认为内容数据与 HttpResponseMessage 一起存储,并且如果我序列化和反序列化就会存在。但是,看起来 HttpResponseMessage 中的 Content 数据更像是一个指针,提供有关如何读取存储在其他位置的值的指令,这些指令由 HttpContent 类中的 ReadAsStringAsync() 函数调用。

所以我的快速解决方法是创建一个包装对象,该对象存储序列化的 HttpResponseMessage 以及 ReadAsStringAsync() 返回的内容结果。这个包装器看起来像这样。

public class WrapperHttpResponse

    public HttpResponseMessage HttpResponseMessage  get; set; 
    public string Content  get; set; 

    public WrapperHttpResponse()
    

    

    public WrapperHttpResponse(HttpResponseMessage httpResponseMessage)
    
        HttpResponseMessage = httpResponseMessage;
        Content = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
    

    public WrapperHttpResponse(HttpResponseMessage httpResponseMessage, string content)
    
        HttpResponseMessage = httpResponseMessage;
        Content = content;
    

此方法有 3 个构造函数,允许我实例化 HttpResponseMessages 的 null、已读和未读实例。然后我重写了我的缓存执行如下。

private async Task<WrapperHttpResponse> RestGetCachedAsync(string query, ILogger logger = null)
    
        string key = $"GET:query";
        WrapperHttpResponse response;

        var cacheResponse = await _cacheService.GetStringValue(key);

        if (cacheResponse != null)
        
            response = JsonConvert.DeserializeObject<WrapperHttpResponse>(cacheResponse, new JsonSerializerSettings
            
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
            );

            if(response.HttpResponseMessage.IsSuccessStatusCode) return response;
        

        response = await RestGetAsync(query, logger);

        if (response.HttpResponseMessage.IsSuccessStatusCode)
        
            await _cacheService.SetStringValue(key, JsonConvert.SerializeObject(response, new JsonSerializerSettings
            
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
            ));
                    

        return response;
    

【讨论】:

以上是关于将序列化的 HttpResponseMessage 缓存到 Redis。读取错误。 “InvalidOperationException:流已被消耗。无法再次读取。”的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .net Core MVC 控制器中返回 HttpResponseMessage 而不将其序列化为 JSON?

将内容放在 HttpResponseMessage 对象中?

如何将 HttpResponseMessage 的 ByteArrayContent 下载为 zip

将 HttpResponseMessage.Content 数据 (ReadAsStringAsync) 限制为特定的最大大小

MemoryStream 到 HttpResponseMessage

在 .NET Core 中从 HttpResponseMessage 转换为 IActionResult