Search code examples
c#httpclientusingwebapi

Why using HttpClient in a using block IS WRONG in WebApi context?


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!


Solution

  • 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.