In my .NET Core console application, this line freezes:
services.GetRequiredService<ISomething>();
in the following code:
interface ISomething { }
class Cool : ISomething
{
public Cool(IHttpClientFactory factory) { }
}
class Program
{
static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
// Freezes here and maxes out memory
ISomething internalApiConnector = host.Services.GetRequiredService<ISomething>();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddHttpClient<IHttpClientFactory>();
services.AddSingleton<ISomething, Cool>();
});
}
and ends up maxing out the memory with eventually crashes the app. What am I missing?
Note:
Cool constructor does not get run, so doesn't appear to be something halting in there.Solution (Thanks all):
serviceCollection.AddHttpClient<IHttpClientFactory>(); needed to be changed to
serviceCollection.AddHttpClient();
and
.BuildServiceProvider() was superflous.
What you're experiencing is a stack overflow exception, caused by the AddHttpClient<IHttpClientFactory> registration. This causes a cyclic dependency which is not detected by MS.DI, causing the unfortunate stack overflow.
To understand why this happens, you need to look at the code for HttpClientBuilderExtensions:
builder.Services.AddTransient<TClient>(s =>
{
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(builder.Name);
var typedClientFactory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
return typedClientFactory.CreateClient(httpClient);
});
As the code shows, a call to AddHttpClient<TClient> results in the registration of a delegate. When that delegate is invoked, an IHttpClientFactory is resolved. From that IHttpClientFactory an HttpClient is created. In your case, however, you specified IHttpClientFactory for your TClient, effectively replacing the original IHttpClientFactory registration. This caused the call to s.GetRequiredService<IHttpClientFactory>() to callback into itself, hence the cyclic dependency and the stack overflow.
To make this more concrete, if we replace TClient with IHttpClientFactory, which is basically what is happening in your code, you can see it more clearly:
// A registration for IHttpClientFactory is made
builder.Services.AddTransient<IHttpClientFactory>(s =>
{
// When the delegate is called, IHttpClientFactory is resolved.
var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
...
});
What you did wrong is supplying IHttpClientFactory as the TClient of AddHttpClient<TClient>. AddHttpClient is meant to register a 'client' class that takes a HttpClient as direct dependency. For instance:
services.AddHttpClient<GitHubApiClient>() // GitHubApiClient depends on HttpClient
You, however, aren't the only party at fault here. Microsoft should have been a better job, because:
AddHttpClient<TClient> method doesn't validate it's preconditions allowing this to happen. As it makes no sense to supply AddHttpClient with an abstract TClient, at the very least it should have prevented this and throw an exception.TClient that contains no HttpClient as constructor dependency, so this should have been detected by AddHttpClient as well.This means that there is room for improvement in the Microsoft stack ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With