Uso de EnsureSuccessStatusCode e tratamento de HttpRequestException que ele lança

104

Qual é o padrão de uso HttpResponseMessage.EnsureSuccessStatusCode()? Ele descarta o conteúdo da mensagem e o lança HttpRequestException, mas não consigo ver como lidar com isso de forma diferente de um genérico Exception. Por exemplo, não inclui o HttpStatusCode, o que teria sido útil.

Existe alguma maneira de obter mais informações sobre isso? Alguém poderia mostrar o padrão de uso relevante de ambos EnsureSuccessStatusCode()e HttpRequestException?

G. Stoynev
fonte

Respostas:

156

O uso idiomático de EnsureSuccessStatusCodeé verificar de forma concisa o sucesso de uma solicitação, quando você não deseja lidar com casos de falha de nenhuma maneira específica. Isso é especialmente útil quando você deseja criar um protótipo de cliente rapidamente.

Quando você decidir que deseja tratar os casos de falha de uma maneira específica, não faça o seguinte.

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

Isso lança uma exceção apenas para detectá-lo imediatamente, o que não faz nenhum sentido. A IsSuccessStatusCodepropriedade de HttpResponseMessageexiste para esse fim. Faça o seguinte.

var response = await client.GetAsync(...);
if (response.IsSuccessStatusCode)
{
    // Handle success
}
else
{
    // Handle failure
}
Escudos Timothy
fonte
1
Existe alguma maneira de obter o código de status do inteiro real ? quando tento fazer isso, obtenho uma string como "NotFound" em vez do código de status 404.
NickG
12
@NickG (int)response.StatusCode(Consulte msdn.microsoft.com/en-us/library/… )
Timothy Shields
1
Observe que a HttpRequestException padrão lançada por EnsureSuccessStatusCode () terá a frase de razão. Mas você pode acessar essa propriedade de qualquer maneira na resposta se não for bem-sucedido.
Stefan Zvonar
@StefanZvonar Não consigo encontrar a frase de razão na exceção conforme o que você escreveu.
KansaiRobot
1
@NickG Você pode usar (int) response.StatusCode para obter o valor numérico para o Código de status HTTP
Henrik Holmgaard Høyer
95

Eu não gosto de VerifySuccessStatusCode, pois não retorna nada significativo. É por isso que criei minha própria extensão:

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

o código-fonte para o VerifySuccessStatusCode da Microsoft pode ser encontrado aqui

Versão síncrona baseada no link 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);
}

O que eu não gosto em IsSuccessStatusCode é que não é "bem" reutilizável. Por exemplo, você pode usar a biblioteca como polly para repetir uma solicitação em caso de problema de rede. Nesse caso, você precisa que seu código levante uma exceção para que polly ou alguma outra biblioteca possa lidar com isso ...

pajics
fonte
1
concordar, o código padrão faltando o recurso para obter uma mensagem significativa do retorno.
LT
2
Sua versão funciona diferente da implementação original de EnsureSuccessStatusCode. Você sempre descarta o response.Content(porque finalmente é chamado sempre mesmo após o return;enunciado) e ele destrói o conteúdo para leitura posterior . A implementação original descarta o conteúdo apenas quando o código de status não indica um resultado bem-sucedido.
Lukas.Navratil
4
Não entendo por que você primeiro await response.Content.ReadAsStringAsync()e depois verifiqueif (response.Content != null)
mafu
3
Polly agora lida com resultados de retorno, bem como exceções, precisamente para ajudar nesse tipo de cenário. Você pode configurar o Polly para proteger as HttpRequestchamadas e configurar a política para lidar com certas exceções e certos HttpResponseCodes. Veja o exemplo no leia-me de Polly aqui
viajante da montanha de
2
Como pode response.Contentser null quando acaba de ter um método chamado?
Ian Warburton
1

Eu uso VerifySuccessStatusCode quando não quero lidar com a exceção no mesmo método.

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

A exceção lançada em GetUserIdAsync será tratada em DoSomethingAsync.

Sérgio Damasceno
fonte
0

Abaixo está minha solução proposta. A única falha é que, como o gerenciador de recursos da estrutura ASP.NET Core é interno à estrutura, não posso reutilizar diretamente as strings de mensagem internacionalizadas da Microsoft, então estou apenas usando o literal de mensagem em inglês literal aqui.

Prós

  • Registra o conteúdo para um erro de servidor 5xx
    • Às vezes, um erro de servidor é, na verdade, um erro de cliente disfarçado, como um cliente usando um endpoint obsoleto que finalmente foi desligado.
  • Torna mais fácil descobrir erros ao escrever testes de integração usando ConfigureTestContainer<T>

Contras

  • Ineficiente.
    • Se você ler o conteúdo da resposta e o conteúdo for muito longo, o cliente ficará lento. Para alguns clientes, com requisitos de resposta em tempo real suave, esse jitter pode ser inaceitável.
  • Responsabilidade incorreta para registro e monitoramento de erros.
    • Se for um erro do servidor 5xx, por que o cliente se importa, já que não fez nada de errado? Basta ligar response.EnsureSuccessStatusCode();e deixar o servidor lidar com isso.
    • Por que não apenas verificar os logs de erro do servidor quando há um erro interno do servidor?
  • Requer a leitura da Contentpropriedade antes de verificar o status. Pode haver situações em que isso não seja desejável, uma das quais é a ineficiência.

Uso

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)
                );
        }
    }
John Zabroski
fonte