Usage of EnsureSuccessStatusCode and handling of HttpRequestException it throws
Asked Answered
R

5

143

What's the usage pattern of HttpResponseMessage.EnsureSuccessStatusCode()? It disposes of the Content of the message and throws HttpRequestException, but I fail to see how to programmatically handle it any differently than a generic Exception. For example, it doesn't include the HttpStatusCode, which would have been handy.

Is there any way of getting more info out of it? Could anyone show relevant usage pattern of both EnsureSuccessStatusCode() and HttpRequestException?

Raeleneraf answered 13/1, 2014 at 17:34 Comment(0)
G
215

The idiomatic usage of EnsureSuccessStatusCode is to concisely verify success of a request, when you don't want to handle failure cases in any specific way. This is especially useful when you want to quickly prototype a client.

When you decide you want to handle failure cases in a specific way, do not do the following.

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

This throws an exception just to immediately catch it, which doesn't make any sense. The IsSuccessStatusCode property of HttpResponseMessage is there for this purpose. Do the following instead.

var response = await client.GetAsync(...);
if (response.IsSuccessStatusCode)
{
    // Handle success
}
else
{
    // Handle failure
}
Gunar answered 16/1, 2015 at 22:30 Comment(5)
Is there any way to get the real integer status code? when I try this I get a string such as "NotFound" instead of the 404 status code.Dinorahdinosaur
@Dinorahdinosaur (int)response.StatusCode (See msdn.microsoft.com/en-us/library/…)Gunar
Note, the default HttpRequestException thrown by EnsureSuccessStatusCode() will have the reason phrase. But you could access that property anyway in the response if it is not successful.Centesimo
@TimothyShields I appreciate your color on allowing rapid prototyping. Why wouldn't you first read the response.Content value before jumping into the // Handle success and // Handle failure blocks? In this way, you only read the response.Content property once. The only downside I can see to doing it this way is if the Content property is a long string, then you're basically slowing down the client, but if you're worried about speed, why not just use response.EnsureSuccessStatusCode();?Aminopyrine
I wrote my own version of EnsureSuccessStatusCode below. https://mcmap.net/q/158959/-usage-of-ensuresuccessstatuscode-and-handling-of-httprequestexception-it-throws It delegates to the caller the responsibility get the Content prior to checking the status.Aminopyrine
D
123

I don't like EnsureSuccessStatusCode as it doesn't return anything meaninful. That is why I've created my own extension:

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

source code for Microsoft's EnsureSuccessStatusCode can be found here

Synchronous version based on SO link :

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

What I don't like about IsSuccessStatusCode is that it is not "nicely" reusable. For example you can use library like polly to repeat a request in case of network issue. In that case you need your code to raise exception so that polly or some other library can handle it...

Duvall answered 16/1, 2015 at 14:43 Comment(9)
agree, the default code missing the feature to get a meaningful message from the return.Antebellum
Your version works different than original implementation of EnsureSuccessStatusCode. You always dispose the response.Content (because finally is called always even after the return; statement) and it destroys the content for futher reading. Original implementation disposes the content only when status code does not indicate successful result.Uruguay
you are right, since I posted I've updated my code locallyDuvall
answered here on the same lines link As @Jookyn says just make sure there are no gap in custom and existing EnsureSuccessStatusCodeAmain
I don't get why you first await response.Content.ReadAsStringAsync() and then check if (response.Content != null)Analog
This approach is deal to putting the error handling right in an if (!response.IsSuccessStatusCode) because where I'm executing the HTTP request isn't necessarily where I know how I want to handle the error.Dupion
Polly now handles return results as well as exceptions, precisely to aid with this kind of scenario. You can configure Polly to protect HttpRequest calls, and configure the policy both to handle certain exceptions, and certain HttpResponseCodes. See the example in the Polly readme hereSouthey
How could response.Content be null when it has just had a method called on it?Heyer
@IanWarburton It does look really weird, but if I remember correctly there are some cases when this happens. Feel free to remove it and let me know if it does happen to be null...Duvall
A
8

For .NET 5.0 or later, HttpRequestException returns HttpStatusCode property, and you should be using that instead.

Below is my proposed solution for pre-.NET 5.0. The only flaw is that since the ASP.NET Core framework resource manager is internal to the framework, I cannot directly re-use Microsoft's internationalized message strings, so I'm just using the verbatim English message literal here.

Pros

  • Logs the content for an 5xx server error
    • Sometimes, a server error is actually a client error in disguise, such as a client using a deprecated endpoint that finally got shut off.
  • Makes it easier to uncover errors when writing integration tests using ConfigureTestContainer<T>

Cons

  • Inefficient.
    • If you read the response content, and the content is very long, you will slow the client down. For some clients, with soft real-time response requirements, this jitter may be unacceptable.
  • Incorrect responsibility for error logging and monitoring.
    • If this is a 5xx server error, why does the client care, since the client did nothing wrong? Just call response.EnsureSuccessStatusCode(); and let the server deal with it.
    • Why not just check the server error logs when there is an Internal Server Error?
  • Requires reading the Content property prior to checking the status. There may be situations where this is not desirable, one of which is inefficiency.

Usage

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)
                );
        }
    }
Aminopyrine answered 18/8, 2020 at 21:6 Comment(0)
H
6

I use EnsureSuccessStatusCode when I don't want to handle the Exception on the same method.

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

The Exception thrown on GetUserIdAsync will be handled on DoSomethingAsync.

Horme answered 10/8, 2019 at 21:6 Comment(0)
S
2

Since Feb 24, 2020 the HttpRequestException.StatusCode property is set by EnsureSuccessStatusCode. If I understand correctly, the suggested extensions in this question are not needed anymore.

Smutchy answered 19/10, 2022 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.