30

We can read here YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE that we should not create and dispose HttpClient for each http request. Instead, it should be cached and reused (e.g as Singleton in DI container). As well in official .NET documentation for HttpClient:

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.

The recommendation is to use HttpClientFactory, but after looking at:

  public interface IHttpClientFactory
  {
    /// <summary>
    /// Creates and configures an <see cref="T:System.Net.Http.HttpClient" /> instance using the configuration that corresponds
    /// to the logical name specified by <paramref name="name" />.
    /// </summary>
    /// <param name="name">The logical name of the client to create.</param>
    /// <returns>A new <see cref="T:System.Net.Http.HttpClient" /> instance.</returns>
    /// <remarks>
    /// <para>
    /// Each call to <see cref="M:System.Net.Http.IHttpClientFactory.CreateClient(System.String)" /> is guaranteed to return a new <see cref="T:System.Net.Http.HttpClient" />
    /// instance. Callers may cache the returned <see cref="T:System.Net.Http.HttpClient" /> instance indefinitely or surround
    /// its use in a <langword>using</langword> block to dispose it when desired.
    /// </para>
    /// <para>
    /// The default <see cref="T:System.Net.Http.IHttpClientFactory" /> implementation may cache the underlying
    /// <see cref="T:System.Net.Http.HttpMessageHandler" /> instances to improve performance.
    /// </para>
    /// <para>
    /// Callers are also free to mutate the returned <see cref="T:System.Net.Http.HttpClient" /> instance's public properties
    /// as desired.
    /// </para>
    /// </remarks>
    HttpClient CreateClient(string name);
  }

it says that each call will always create a HttpClient instance and caller may cache it.

Each call to IHttpClientFactory.CreateClient is guaranteed to return a new HttpClient instance. Callers may cache the returned instance indefinitely or surround its use in a using block to dispose it when desired.

So question is should I completely rely on HttpClientFactory or I should still cache created HttpClient from it?

In our project we use HttpClientFactory.CreateClient every time we make request and he will still having socket exceptions.

mardok
  • 2,135
  • 2
  • 23
  • 37

2 Answers2

34

HttpClient is only IDisposable because its HttpMessageHandler is IDisposable. In reality, it's the HttpMessageHandler which should be long-lived.

HttpClientFactory works by keeping a long-lived HttpMessageHandler internally. Whenever you ask for a HttpClient, it uses the long-lived HttpMessageHander, and tells the HttpClient not to dispose it when the HttpClient is disposed.

You can see that on GitHub:

public HttpClient CreateClient(string name)
{
    // ...

    // Get a cached HttpMessageHandler
    var handler = CreateHandler(name);

    // Give it to a new HttpClient, and tell it not to dispose it
    var client = new HttpClient(handler, disposeHandler: false);

    // ...

    return client;
}

So, technically it doesn't matter whether you cache the HttpClient or dispose it straight away - disposing it doesn't do anything (because it's been told not to dispose its HttpClientHandler, as that's managed by the HttpClientFactory).

Regarding disposing the HttpClient, MSDN says:

Disposal of the client isn't required. Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory tracks and disposes resources used by HttpClient instances. The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. This pattern becomes unnecessary after migrating to IHttpClientFactory.

I suspect the SocketExceptions you're seeing have a different cause. Perhaps ask a new question focussed on them?

Community
  • 1
  • 1
canton7
  • 37,633
  • 3
  • 64
  • 77
  • (facepalm) I'm investigating a user bug that doesn't actually exist so my brain has turned into a black hole – Panagiotis Kanavos Feb 08 '19 at 17:33
  • @PanagiotisKanavos I did find it odd that your line of reasoning in the comments here was the opposite of what you were saying in the comments on the original question! No worries, I'll delete my responses. – canton7 Feb 08 '19 at 17:34
5

Things have changed in a good way in the ASP.NET Core 2.2 release. The way the HttpClient is expected to be consumed is through DI only, which internally handles all the necessary caching for you using HttpClientFactory. The following documentation article has been updated to reflect on these new use cases: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2

Also, @RyanNowak from ASP.NET Core team has covered all these changes in the following ASP.Net Core Community Standup session: https://www.youtube.com/watch?v=Lb12ZtlyMPg If you haven't watched it, I strongly recommend watching it, as it's super informative and educating.

Here is a small sample to showcase the usage. In the Startup.ConfigureServices method call:

services.AddHttpClient();

Note: There are multiple usage patterns, this is the most basic one. Look into the docs for other patterns, which may suite your needs better.

Later, in the class, where from you'd like to make http requests, take a dependency on IHttpClientFactory and let DI instantiate it for you as necessary. Here is the sample from Microsoft Docs:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/aspnet/docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}
Artak
  • 2,819
  • 20
  • 31
  • I'm having similar problems with open sockets. You can read my symptoms in https://github.com/dotnet/corefx/issues/35698 . Although `HttpMessageHandler` seem to be reused, I still have many new connections. I even have a question about it https://stackoverflow.com/questions/54931543/asp-net-core-httpclient-has-many-time-wait-or-close-wait-connections – Ahmet Mar 03 '19 at 23:45