4

So, the question is why the usage of HttpClient in using block is WRONG, BUT in WebApi context?

I've been reading this article Don't Block on Async Code. In it we have the following example:

public static async Task<JObject> GetJsonAsync(Uri uri)
{
  // (real-world code shouldn't use HttpClient in a using block; this is just example code)
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public class MyController : ApiController
{
  public string Get()
  {
    var jsonTask = GetJsonAsync(...);
    return jsonTask.Result.ToString();
  }
}

The comment // (real-world code shouldn't use HttpClient in a using block; this is just example code) just triggered me. I've been always using HttpClient in this way.

The next thing I've checked is Microsoft's documentation on HttpClient Class. In it, we have the following statement with provided source sample:

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.

public class GoodController : ApiController
{
    private static readonly HttpClient HttpClient;

    static GoodController()
    {
        HttpClient = new HttpClient();
    }
}

So isn't the constructor called on each request and thus a new HttpClient will be created every time?

Thanks!

Rostech
  • 322
  • 1
  • 11

2 Answers2

9

There's a bit of a long answer to this...

Originally, the official recommendation was to use HttpClient in a using block. But this caused problems at scale, essentially using up lots of connections in the TIME_WAIT state.

So, the official recommendation changed to use a static HttpClient. But this caused problems where it would never correctly handle DNS updates.

So, the ASP.NET team came up with IHttpClientFactory in .NET Core 2.1, so code (or at least code running on modern platforms) can reuse HttpClient instances (or, more properly, the message handlers of those instances), avoiding the TIME_WAIT problem, but also periodically closing those connections to avoid the DNS problem.

But, at the same time, the .NET team came up with SocketsHttpHandler also in .NET Core 2.1, which also does connection pooling.

So, on modern platforms, you can either use IHttpClientFactory or a static/singleton HttpClient. On older platforms (including .NET Framework), you would use a static/singleton HttpClient and either live with the DNS issue or use other workarounds.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

Actually writing this question I noticed the static constructor in the code sample provided from Microsoft. This all makes sense now.

The Static Constructors are used to initialize any static data, or to perform a particular action that needs to be performed only once. It is called automatically before the first instance is created or any static members are referenced.

In the context of WebAPI the static constructor is called one time only thus creating only one HttpClient and reusing it for all other requests.

I'll never use using(HttpClient....) in production code again.

This is a great article on the wrong usage of HttpClient - YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE

Rostech
  • 322
  • 1
  • 11
  • 2
    Also see the excellent articles: [You're Using HttpClient Wrong](https://visualstudiomagazine.com/blogs/tool-tracker/2019/09/using-http.aspx) , [You're using HttpClient wrong and it is destabilizing your software](https://www.westerndevs.com/Deployment/httpclientwrong/) , [You're (probably still) using HttpClient wrong and it is destabilizing your software](https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/) – Fildor Apr 16 '21 at 14:54
  • Thanks, just remembered another nice article related to the topic. – Rostech Apr 16 '21 at 14:58