Unable to resolve service for type 'System.Net.Http.HttpClient'
TLDR;
ViewComponent
s do not support typed clients out of the box. To resolve this, add a call to AddViewComponentsAsServices()
onto the end of the call to services.AddMvc(...)
.
After a pretty long chat that ran off the back of being able to reproduce your issue, we determined initially that the problem being observed is specific to ViewComponent
s. Even with a call to IServiceCollection.AddHttpClient<SomeViewComponent>()
, passing an instance of HttpClient
into SomeViewComponent
s constructor just refused to work.
However, sitting a new class (SomeService
) between SomeComponent
and HttpClient
works as expected. This is what the docs refer to as a typed client. The code looks a bit like this:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeService>();
// ...
}
// SomeService.cs
public class SomeService
{
public SomeService(HttpClient httpClient)
{
// ...
}
}
// SomeViewComponent.cs
public class SomeViewComponent
{
public SomeViewComponent(SomeService someService)
{
// ...
}
}
As I've already stated, this approach works - the ASP.NET Core DI system is very happy to create the instance of SomeService
and its typed HttpClient
instance.
To restate the original problem, take the following example code:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeViewComponent>();
// ...
}
public class SomeViewComponent
{
public SomeViewComponent(HttpClient httpClient)
{
// ...
}
}
In this case, the ASP.NET Core DI system refuses to create an instance of SomeViewComponent
due to not being able to resolve HttpClient
. It turns out that this is not specific just to ViewComponent
s: it also applies to Controller
s and TagHelper
s (thanks to Chris Pratt for confirming for TagHelper
s).
Interestingly, the following also works:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<SomeViewComponent>();
// ...
}
public class SomeViewComponent
{
public SomeViewComponent(IHttpClientFactory httpClientFactory)
{
var httpClient = httpClientFactory.CreateClient("SomeViewComponent")
// ...
}
}
In this example, we're taking advantage of the fact that the call to AddHttpClient<SomeViewComponent>
registered a named client for us.
In order to be able to inject HttpClient
directly into a ViewComponent
, we can add a call to AddViewComponentsAsServices
when we register MVC with DI:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(...)
.AddViewComponentsAsServices();
// ...
}
AddControllersAsServices
and AddTagHelpersAsServices
can also be called to add the same support for Controller
s and TagHelpers
respectively.
If we look at the docs more closely, it's clear that none of the examples there inject a HttpClient
into Controller
s et al - there's simply no mention of this approach at all.
Unfortunately, I don't know enough about the ASP.NET Core DI system in order to be able to explain exactly why this works the way it does: The information I've provided above simply explains the what along with a solution. Chris Pratt has opened an issue in Github for the docs to be updated to expand upon this.
I had a similar problem - the problem was in double registration:
services.AddHttpClient<Service>();
services.AddSingleton<Service>(); // fixed by removing this line
Similar examples [just adding to clarify that it's not specific to AddSingleton, nor related to the order.]
services.AddScoped<IService, Service>(); // fixed by removing this line
services.AddHttpClient<IService, Service>();