Użycie CompareSuccessStatusCode i obsługa zgłaszanego przez niego HttpRequestException

105

Jaki jest wzorzec użytkowania HttpResponseMessage.EnsureSuccessStatusCode()? Pozbywa się treści wiadomości i wyrzuca HttpRequestException, ale nie widzę, jak programowo obsłużyć ją inaczej niż rodzajowy Exception. Na przykład nie zawiera HttpStatusCode, co byłoby przydatne.

Czy jest jakiś sposób, aby uzyskać więcej informacji? Czy ktoś mógłby pokazać odpowiedni wzorzec użycia obu EnsureSuccessStatusCode()i HttpRequestException?

G. Stoynev
źródło

Odpowiedzi:

157

Idiomatyczne użycie EnsureSuccessStatusCodeto zwięzłe zweryfikowanie sukcesu żądania, gdy nie chcesz zajmować się przypadkami niepowodzeń w żaden określony sposób. Jest to szczególnie przydatne, gdy chcesz szybko prototypować klienta.

Jeśli zdecydujesz, że chcesz obsługiwać przypadki awarii w określony sposób, nie wykonuj następujących czynności.

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

To rzuca wyjątek tylko po to, aby go natychmiast złapać, co nie ma żadnego sensu. W tym celu służy IsSuccessStatusCodewłasność firmy HttpResponseMessage. Zamiast tego wykonaj następujące czynności.

var response = await client.GetAsync(...);
if (response.IsSuccessStatusCode)
{
    // Handle success
}
else
{
    // Handle failure
}
Timothy Shields
źródło
1
Czy istnieje sposób na uzyskanie rzeczywistego kodu statusu liczby całkowitej ? kiedy próbuję tego, otrzymuję ciąg znaków, taki jak „NotFound” zamiast kodu stanu 404.
NickG,
12
@NickG (int)response.StatusCode(patrz msdn.microsoft.com/en-us/library/… )
Timothy Shields
1
Uwaga: domyślny wyjątek HttpRequestException generowany przez RememberSuccessStatusCode () będzie miał frazę powodu. Ale i tak można uzyskać dostęp do tej właściwości w odpowiedzi, jeśli się nie powiedzie.
Stefan Zvonar,
@StefanZvonar Nie mogę znaleźć wyrażenia powodu w wyjątku, tak jak napisałeś.
KansaiRobot
1
@NickG Możesz użyć (int) response.StatusCode do uzyskania wartości liczbowej dla kodu stanu HTTP
Henrik Holmgaard Høyer
95

Nie podoba mi się RememberSuccessStatusCode, ponieważ nie zwraca nic sensownego. Dlatego stworzyłem własne rozszerzenie:

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

kod źródłowy dla programu GuaranteeSuccessStatusCode firmy Microsoft można znaleźć tutaj

Wersja synchroniczna oparta na łączu 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);
}

To, co mi się nie podoba w IsSuccessStatusCode, to fakt, że nie można go „ładnie” ponownie wykorzystać. Na przykład możesz użyć biblioteki takiej jak Polly, aby powtórzyć żądanie w przypadku problemu z siecią. W takim przypadku potrzebujesz kodu do zgłaszania wyjątku, aby Polly lub inna biblioteka mogła go obsłużyć ...

pajics
źródło
1
zgadzam się, domyślny kod nie ma funkcji, aby uzyskać znaczący komunikat po powrocie.
LT
2
Twoja wersja działa inaczej niż oryginalna implementacja EnsureSuccessStatusCode. Zawsze usuwasz response.Content(ponieważ w końcu jest wywoływany zawsze nawet po return;oświadczeniu) i niszczy treść do dalszego czytania. Oryginalna implementacja usuwa zawartość tylko wtedy, gdy kod stanu nie wskazuje pomyślnego wyniku.
Lukas.Navratil
4
Nie rozumiem, dlaczego najpierw await response.Content.ReadAsStringAsync()sprawdzaszif (response.Content != null)
mafu
3
Polly obsługuje teraz zwracane wyniki, a także wyjątki, właśnie po to, aby pomóc w tego rodzaju scenariuszach. Możesz skonfigurować Polly do ochrony HttpRequestpołączeń i skonfigurować zasady zarówno do obsługi określonych wyjątków, jak i niektórych HttpResponseCode. Zobacz przykład w pliku Readme Polly tutaj
podróżnik górski
2
Jak może response.Contentbyć null, skoro właśnie wywołano metodę?
Ian Warburton,
1

Używam CompareSuccessStatusCode, gdy nie chcę obsługiwać wyjątku w tej samej metodzie.

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

Wyjątek zgłoszony w GetUserIdAsync będzie obsługiwany w DoSomethingAsync.

Sérgio Damasceno
źródło
0

Poniżej przedstawiam proponowane przeze mnie rozwiązanie. Jedyną wadą jest to, że ponieważ menedżer zasobów platformy ASP.NET Core jest wewnętrzny dla platformy, nie mogę bezpośrednio ponownie używać umiędzynarodowionych ciągów komunikatów firmy Microsoft, więc używam tutaj po prostu dosłownego angielskiego literału wiadomości.

Plusy

  • Rejestruje zawartość pod kątem błędu serwera 5xx
    • Czasami błąd serwera jest w rzeczywistości ukrytym błędem klienta, na przykład klient korzystający z przestarzałego punktu końcowego, który ostatecznie został wyłączony.
  • Ułatwia wykrywanie błędów podczas pisania testów integracji przy użyciu ConfigureTestContainer<T>

Cons

  • Nieskuteczny.
    • Jeśli przeczytasz treść odpowiedzi, a treść jest bardzo długa, spowolnisz klienta. W przypadku niektórych klientów z miękkimi wymaganiami odpowiedzi w czasie rzeczywistym ten jitter może być nie do zaakceptowania.
  • Niewłaściwa odpowiedzialność za rejestrowanie i monitorowanie błędów.
    • Jeśli jest to błąd serwera 5xx, dlaczego klient się tym przejmuje, skoro klient nie zrobił nic złego? Po prostu zadzwoń response.EnsureSuccessStatusCode();i pozwól serwerowi się tym zająć.
    • Dlaczego nie sprawdzić dzienników błędów serwera, gdy występuje wewnętrzny błąd serwera?
  • Wymaga zapoznania się z Contentnieruchomością przed sprawdzeniem statusu. Mogą zaistnieć sytuacje, w których nie jest to pożądane, a jedną z nich jest nieefektywność.

Stosowanie

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
źródło