☁️Properly Using HttpClient with C# in .NET Standard 2.0

The correct and proper way to use HttpClient with C# in .NET Standard 2.0 to avoid both socket exhaustion and DNS updates.

☁️Properly Using HttpClient with C#  in .NET Standard 2.0
StandardSocketsHttpHandler - the key to proper HttpClient in .NET Standard 2.0

If you are not yet aware of the issues with HttpClient in .NET here's a brief summary:

  • HttpClient implements IDisposable, therefore, intuitively 🤷‍♂️, it should be disposed after usage.
  • Disposing it leads to socket exhaustion. That's because the internal http handler uses a connection from a connection pool that doesn't dispose along with the HttpClient.
  • Not Disposing, obviously, leads to memory issues.
  • Not Disposing + single instance of HttpClient (static readonly new HttpClient) works, and it's the quickest fix, BUT it misses any DNS updates that happen after instantiation.

Here's the clean fix, in .NET Standard 2.0

Install the StandardSocketsHttpHandler NuGet. In .netcore2.1+ this is not needed as the framework itself has this implementation embedded (SocketsHttpHandler).

dotnet add package StandardSocketsHttpHandler --version 2.2.0.4

Instantiate your HttpClient as follows:

static HttpClient BuildNewHttpClient()
{
	return
		new HttpClient
		(
			handler: BuildNewStandardSocketsHttpHandler(),
			disposeHandler: true
		);
}

static StandardSocketsHttpHandler BuildNewStandardSocketsHttpHandler()
{
	return
		new StandardSocketsHttpHandler()
		{
			// The maximum idle time for a connection in the pool. When there is no request in
			// the provided delay, the connection is released.
			// Default value in .NET 6: 1 minute
			PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),

			// This property defines maximal connection lifetime in the pool regardless
			// of whether the connection is idle or active. The connection is reestablished
			// periodically to reflect the DNS or other network changes.
			// ⚠️ Default value in .NET 6: never
			//    Set a timeout to reflect the DNS or other network changes
			PooledConnectionLifetime = TimeSpan.FromHours(.5),
		};
}

Safely use it as IDisposable

using (HttpClient http = BuildNewHttpClient())
{
	[...]
}

I don't own this solution, I found the original idea here: https://www.meziantou.net/avoid-dns-issues-with-httpclient-in-dotnet.htm


Another fix, that I personally don't like that much is fiddling with ServicePointManager and ConnectionLeaseTimeout

var sp = ServicePointManager.FindServicePoint(new Uri("http://foo.bar/baz/123?a=ab"));
sp.ConnectionLeaseTimeout = 60*1000; // 1 minute

as described here: http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html

I personally haven't even tried this workaround, but I guess it works... 🤷‍♂️