首页 > 解决方案 > Caching Serialized HttpResponseMessage to Redis. Error on read. "InvalidOperationException: The stream was already consumed. It cannot be read again."

问题描述

I have a function that takes an API request, checks if the result exists in a Redis cache, if it does it returns the cached value, if not it sends the API request and then caches the value.

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;
    }

When reading a previously queried API

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

I get the following error.

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

标签: c#redishttpresponsemessage

解决方案


在对问题的评论进行讨论后,我意识到我的错。我认为内容数据与 HttpResponseMessage 一起存储,并且如果我序列化和反序列化就会存在。但是,看起来 HttpResponseMessage 中的内容数据更像是一个指针,它提供有关如何读取存储在其他地方的值的指令,这些指令由 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;
    }

推荐阅读