runtime: Singleton HttpClient unable to execute GetAsync with .NET Core 2.1 in some environment

We just migrated our (ASP).NET Core apps from 1.1 to 2.1. As we’re using OpenIdConnect, we use an HttpClient for getting the configuration JSON from the identity provider, as well as the user information once he’s logged in.

For efficiency reasons, we create an HttpClient on Startup and register it as a singleton. I’m conscious we should actually use HttpClientFactory by now - which I did, but I still see the same problem.

Now, on development and testing environments, everything works even after migration. However, on the customer’s testing environment - where everything worked before with 1.1 as well - the HttpClient is no longer able to connect to the ID provider, yielding the following exception:

System.Net.Http.HttpRequestException: The requested name is valid, but no data of the requested type was found ---> System.Net.Sockets.SocketException: The requested name is valid, but no data of the requested type was found
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at Bedag.Net.Core.Service.Auth.OpenIdConnect.OpenIdConnectDiscovery.ObtainConfiguration()

As I already wrote, everything works on our development and testing environments, but not on the customer’s environment. I’ve been trying to figure out configurations that are completely different, but couldn’t find any. So I started playing around with code and .NET Core options. And I even found some workarounds. Here’s what did and didn’t work:

Works:

  • Using DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER environment variable and setting it to 0
  • Initializing the HttpClient for every request (this works no matter if I set the environment variable or not)

Doesn’t work:

  • Injecting IHttpClientFactory and then using CreateClient for any request
  • Using a static HttpClient

So this is confusing. If I initialize an HttpClient for every request, it works even when using SocketsHttpHandler (of course, it works as well when using WinHttpHandler). However, if the HttpClient (or the HttpClientFactory) is initialized during Startup, the code only works when we’re NOT using SocketsHttpHandler, but does work with WinHttpHandler. My guess is that the new SocketsHttpHandler requires some parameters that do not seem to be available on Startup, but that WinHttpHandler does not need (or gets them in another way). Because using SocketsHttpHandler does work when used “later” in the code, I guess its initialization on Startup is incomplete.

Can you give me any hints on this? It’s just confusing that the apps are working on some environments and not on others (all machines are running Windows Server 2012 R2 with IIS 8.5).

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 27 (15 by maintainers)

Most upvoted comments

1 hour ago 😉

Just a short update on this… I’ve already tried various approaches to reproduce this, but the only one that was “successful” was using our own stack, which I won’t publish here. I therefore still need to figure out what exactly is causing this behavior. It might be the fact that we’re using a hierarchy of Startup classes; that’s what I’m going to try next. We’re also using SimpleInjector instead of the standard DI implementation, but this apparently wasn’t the cause. I’m continuing to work on this one…