Кэширование сериализованного сообщения HttpResponse в Redis. Ошибка при чтении. «InvalidOperationException: поток уже использовался. Он не может быть прочитан снова.» - PullRequest
0 голосов
/ 28 января 2020

У меня есть функция, которая принимает запрос 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.

1 Ответ

0 голосов
/ 29 января 2020

После обсуждения в комментариях к вопросу я осознал свою вину. Я думал, что данные контента хранятся с помощью HttpRequestMessage и будут там, если я сериализовал и десериализовал. Однако, похоже, что данные Content в HttpRequestMessage больше похожи на указатель, который предоставляет инструкции о том, как прочитать значение, хранящееся в другом месте, и эти инструкции вызываются функцией ReadAsStringAsyn c () в классе HttpContent.

Итак, мое быстрое решение состояло в том, чтобы создать объект-обертку, в котором будет храниться сериализованный HttpResponseMessage, а также результат Content, возвращаемый ReadAsStringAsyn c (). Эта оболочка выглядит следующим образом.

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 конструктора, что позволяет мне создавать экземпляры null, read и unread экземпляров HttpResponseMessages. Затем я переписал свое выполнение кэширования следующим образом.

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