Uso de SecureSuccessStatusCode y manejo de HttpRequestException que arroja

104

¿Cuál es el patrón de uso de HttpResponseMessage.EnsureSuccessStatusCode()? Elimina el contenido del mensaje y lo lanza HttpRequestException, pero no veo cómo manejarlo programáticamente de manera diferente a un genérico Exception. Por ejemplo, no incluye el HttpStatusCode, que habría sido útil.

¿Hay alguna forma de obtener más información? ¿Alguien podría mostrar un patrón de uso relevante de ambos EnsureSuccessStatusCode()y HttpRequestException?

G. Stoynev
fuente

Respuestas:

156

El uso idiomático de EnsureSuccessStatusCodees verificar de manera concisa el éxito de una solicitud, cuando no desea manejar los casos de falla de una manera específica. Esto es especialmente útil cuando desea crear rápidamente un prototipo de un cliente.

Cuando decida que quiere manejar los casos de falla de una manera específica, no haga lo siguiente.

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

Esto arroja una excepción solo para detectarlo de inmediato, lo que no tiene ningún sentido. La IsSuccessStatusCodepropiedad de HttpResponseMessageestá ahí para este propósito. En su lugar, haga lo siguiente.

var response = await client.GetAsync(...);
if (response.IsSuccessStatusCode)
{
    // Handle success
}
else
{
    // Handle failure
}
Timothy Shields
fuente
1
¿Hay alguna forma de obtener el código de estado entero real ? cuando intento esto, obtengo una cadena como "NotFound" en lugar del código de estado 404.
NickG
12
@NickG (int)response.StatusCode(Ver msdn.microsoft.com/en-us/library/… )
Timothy Shields
1
Tenga en cuenta que la HttpRequestException predeterminada lanzada por GuaranteSuccessStatusCode () tendrá la frase de motivo. Pero puede acceder a esa propiedad de todos modos en la respuesta si no tiene éxito.
Stefan Zvonar
@StefanZvonar No puedo encontrar la frase del motivo en la excepción como lo que escribiste.
KansaiRobot
1
@NickG Puede usar (int) response.StatusCode para obtener el valor numérico del código de estado HTTP
Henrik Holmgaard Høyer
95

No me gusta GuaranteeSuccessStatusCode ya que no devuelve nada significativo. Por eso he creado mi propia extensión:

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

El código fuente para SecureSuccessStatusCode de Microsoft se puede encontrar aquí.

Versión sincrónica basada en el enlace 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);
}

Lo que no me gusta de IsSuccessStatusCode es que no es "agradablemente" reutilizable. Por ejemplo, puede usar una biblioteca como polly para repetir una solicitud en caso de un problema de red. En ese caso, necesita su código para generar una excepción para que polly o alguna otra biblioteca pueda manejarlo ...

pajics
fuente
1
de acuerdo, el código predeterminado falta la función para obtener un mensaje significativo de la devolución.
LT
2
Tu versión funciona de manera diferente a la implementación original de EnsureSuccessStatusCode. Siempre elimina el response.Content(porque finalmente se llama siempre incluso después de la return;declaración) y destruye el contenido para su posterior lectura. La implementación original elimina el contenido solo cuando el código de estado no indica un resultado exitoso.
Lukas.Navratil
4
No entiendo por qué primero await response.Content.ReadAsStringAsync()y luego revisaif (response.Content != null)
mafu
3
Polly ahora maneja los resultados devueltos así como las excepciones, precisamente para ayudar con este tipo de escenario. Puede configurar Polly para proteger las HttpRequestllamadas y configurar la política para manejar ciertas excepciones y ciertos HttpResponseCodecorreos electrónicos. Vea el ejemplo en el archivo Léame de Polly aquí
viajero de montaña
2
¿Cómo podría response.Contentser nulo cuando se le acaba de llamar a un método?
Ian Warburton
1

Utilizo SecureSuccessStatusCode cuando no quiero manejar la excepción con el mismo 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();
        ...
    }
}

La excepción lanzada en GetUserIdAsync se manejará en DoSomethingAsync.

Sérgio Damasceno
fuente
0

A continuación se muestra mi solución propuesta. El único defecto es que, dado que el administrador de recursos del marco ASP.NET Core es interno del marco, no puedo reutilizar directamente las cadenas de mensajes internacionalizadas de Microsoft, por lo que solo estoy usando el literal de mensaje en inglés literal aquí.

Pros

  • Registra el contenido de un error de servidor 5xx
    • A veces, un error del servidor es en realidad un error del cliente disfrazado, como un cliente que usa un punto final obsoleto que finalmente se apagó.
  • Facilita la detección de errores al escribir pruebas de integración utilizando ConfigureTestContainer<T>

Contras

  • Ineficiente.
    • Si lee el contenido de la respuesta y el contenido es muy extenso, ralentizará al cliente. Para algunos clientes, con requisitos suaves de respuesta en tiempo real, este jitter puede ser inaceptable.
  • Responsabilidad incorrecta por el registro y monitoreo de errores.
    • Si se trata de un error del servidor 5xx, ¿por qué le importa al cliente, ya que el cliente no hizo nada malo? Simplemente llame response.EnsureSuccessStatusCode();y deje que el servidor se encargue de ello.
    • ¿Por qué no simplemente comprobar los registros de errores del servidor cuando hay un error interno del servidor?
  • Requiere leer la Contentpropiedad antes de verificar el estado. Puede haber situaciones en las que esto no sea deseable, una de las cuales es la ineficiencia.

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
fuente