Использование EnsureSuccessStatusCode и обработка генерируемого HttpRequestException

105

Какая схема использования HttpResponseMessage.EnsureSuccessStatusCode()? Он удаляет содержимое сообщения и выбрасывает HttpRequestException, но я не вижу, как программно обрабатывать его иначе, чем общий Exception. Например, он не включает в себя HttpStatusCode, что было бы удобно.

Есть ли способ получить от этого больше информации? Может ли кто-нибудь показать соответствующий шаблон использования как для EnsureSuccessStatusCode()HttpRequestException?

Г. Стойнев
источник

Ответы:

157

Идиоматическое использование EnsureSuccessStatusCode- это краткая проверка успеха запроса, когда вы не хотите обрабатывать случаи сбоя каким-либо определенным образом. Это особенно полезно, когда вы хотите быстро создать прототип клиента.

Когда вы решите, что хотите обрабатывать случаи сбоя определенным образом, не делайте следующего.

var response = await client.GetAsync(...);
try
{
    response.EnsureSuccessStatusCode();
    // Handle success
}
catch (HttpRequestException)
{
    // Handle failure
}

Это вызывает исключение, чтобы сразу его поймать, что не имеет никакого смысла. Для этого есть IsSuccessStatusCodeсобственность HttpResponseMessage. Вместо этого сделайте следующее.

var response = await client.GetAsync(...);
if (response.IsSuccessStatusCode)
{
    // Handle success
}
else
{
    // Handle failure
}
Тимоти Шилдс
источник
1
Есть ли способ получить настоящий целочисленный код статуса? когда я пытаюсь это сделать, я получаю строку типа «NotFound» вместо кода состояния 404.
NickG
12
@NickG (int)response.StatusCode(см. Msdn.microsoft.com/en-us/library/… )
Тимоти Шилдс
1
Обратите внимание, что в HttpRequestException по умолчанию, созданном EnsureSuccessStatusCode (), будет фраза причины. Но вы все равно можете получить доступ к этому свойству в ответе, если он не удастся.
Stefan
@StefanZvonar Я не могу найти фразу причины в исключении из того, что вы написали.
KansaiRobot
1
@NickG Вы можете использовать (int) response.StatusCode для получения числового значения для кода состояния HTTP
Хенрик Холмгаард Хойер,
95

Мне не нравится EnsureSuccessStatusCode, поскольку он не возвращает ничего значимого. Поэтому я создал собственное расширение:

public static class HttpResponseMessageExtensions
{
    public static async Task EnsureSuccessStatusCodeAsync(this HttpResponseMessage response)
    {
        if (response.IsSuccessStatusCode)
        {
            return;
        }

        var content = await response.Content.ReadAsStringAsync();

        if (response.Content != null)
            response.Content.Dispose();

        throw new SimpleHttpResponseException(response.StatusCode, content);
    }
}

public class SimpleHttpResponseException : Exception
{
    public HttpStatusCode StatusCode { get; private set; }

    public SimpleHttpResponseException(HttpStatusCode statusCode, string content) : base(content)
    {
        StatusCode = statusCode;
    }
}

исходный код для Microsoft EnsureSuccessStatusCode можно найти здесь

Синхронная версия на основе ссылки SO :

public static void EnsureSuccessStatusCode(this HttpResponseMessage response)
{
    if (response.IsSuccessStatusCode)
    {
        return;
    }

    var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();

    if (response.Content != null)
        response.Content.Dispose();

    throw new SimpleHttpResponseException(response.StatusCode, content);
}

Что мне не нравится в IsSuccessStatusCode, так это то, что его нельзя "красиво" повторно использовать. Например, вы можете использовать библиотеку наподобие polly, чтобы повторить запрос в случае проблем с сетью. В этом случае вам нужно, чтобы ваш код генерировал исключение, чтобы polly или какая-либо другая библиотека могла его обработать ...

пижамы
источник
1
согласен, в коде по умолчанию отсутствует функция получения значимого сообщения от возврата.
LT
2
Ваша версия работает иначе, чем оригинальная реализация EnsureSuccessStatusCode. Вы всегда удаляете response.Content(потому что finally всегда вызывается даже после return;оператора), и он уничтожает содержимое для дальнейшего чтения. Исходная реализация удаляет контент только тогда, когда код состояния не указывает на успешный результат.
Lukas.Navratil
4
Я не понимаю, почему вы сначала, await response.Content.ReadAsStringAsync()а потом проверяетеif (response.Content != null)
mafu 09
3
Polly теперь обрабатывает возвращаемые результаты, а также исключения, чтобы помочь в этом сценарии. Вы можете настроить Polly для защиты HttpRequestвызовов и настроить политику как для обработки определенных исключений, так и определенных HttpResponseCodes. См. Пример в readme Polly здесь
горный путешественник
2
Как может response.Contentбыть null, если для него только что был вызван метод?
Ian Warburton
1

Я использую EnsureSuccessStatusCode, когда не хочу обрабатывать исключение тем же методом.

public async Task DoSomethingAsync(User user)
{
    try
    {
        ...
        var userId = await GetUserIdAsync(user)
        ...
    }
    catch(Exception e)
    {
        throw;
    }
}

public async Task GetUserIdAsync(User user)
{
    using(var client = new HttpClient())
    {
        ...
        response = await client.PostAsync(_url, context);

        response.EnsureSuccesStatusCode();
        ...
    }
}

Исключение, созданное в GetUserIdAsync, будет обработано в DoSomethingAsync.

Сержио Дамаскено
источник
0

Ниже представлено мое предлагаемое решение. Единственный недостаток заключается в том, что, поскольку диспетчер ресурсов платформы ASP.NET Core является внутренним по отношению к платформе, я не могу напрямую повторно использовать интернационализированные строки сообщений Microsoft, поэтому здесь я просто использую буквальный буквальный текст сообщения на английском языке.

Плюсы

  • Регистрирует содержимое для ошибки сервера 5xx
    • Иногда ошибка сервера на самом деле является замаскированной ошибкой клиента, например, когда клиент использует устаревшую конечную точку, которая наконец отключилась.
  • Облегчает обнаружение ошибок при написании интеграционных тестов с использованием ConfigureTestContainer<T>

Минусы

  • Неэффективно.
    • Если вы читаете содержимое ответа, а оно очень длинное, вы замедляете работу клиента. Для некоторых клиентов, которым требуется мягкий отклик в реальном времени, этот джиттер может быть неприемлемым.
  • Неправильная ответственность за регистрацию ошибок и мониторинг.
    • Если это ошибка сервера 5xx, почему это нужно клиенту, если он не сделал ничего плохого? Просто позвоните response.EnsureSuccessStatusCode();и позвольте серверу разобраться с этим.
    • Почему бы просто не проверить журналы ошибок сервера при возникновении внутренней ошибки сервера?
  • ContentПеред проверкой статуса требуется прочитать свойство. Возможны ситуации, когда это нежелательно, одна из которых - неэффективность.

использование

using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, "controller/action"))
{
  using (var response = await HttpClient.SendAsync(requestMessage))
  {
    var content = await response.Content.ReadAsStringAsync();
    response.EnsureSuccessStatusCode2(content);
    var result = JsonConvert.DeserializeObject<ResponseClass>(content);
  }
}

API

    public static class HttpResponseMessageExtensions
    {
        public static void EnsureSuccessStatusCode2(this HttpResponseMessage message, string content = null)
        {
            if (message.IsSuccessStatusCode)
                return;
            var contentMessage = string.IsNullOrWhiteSpace(content) ? string.Empty : $"Content: {content}";
            throw new HttpRequestException(string.Format(
                System.Globalization.CultureInfo.InvariantCulture,
                "Response status code does not indicate success: {0} ({1}).{2}",
                (int)message.StatusCode,
                message.ReasonPhrase,
                contentMessage)
                );
        }
    }
Джон Заброски
источник