Reputation: 1339
I created a ViewComponent
class which call a REST API
using the HttpClient
, this is the code:
public class ProductsViewComponent : ViewComponent
{
private readonly HttpClient _client;
public ProductsViewComponent(HttpClient client)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task<IViewComponentResult> InvokeAsync(string date)
{
using(var response = await _client.GetAsync($"/product/get_products/{date}"))
{
response.EnsureSuccessStatusCode();
var products = await response.Content.ReadAsAsync<List<Products>>();
return View(products);
}
}
}
I get this error:
InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate MyApp.ViewComponents.ProductsViewComponent'
I injected the HttpClient
in the ConfigureService
method available in Startup
in this way:
services.AddHttpClient<FixturesViewComponent>(options =>
{
options.BaseAddress = new Uri("http://80.350.485.118/api/v2");
});
UPDATE:
I registered the ProductsViewComponent
too, same error.
Upvotes: 45
Views: 112701
Reputation: 23
if you are up version .net 6.0 add below code in to program.cs
builder.Services.AddHttpClient();
Upvotes: 0
Reputation: 1070
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>();
Upvotes: 97
Reputation: 2605
Maybe it will help, but in my situation this worked:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService,MyService>(); // my usual DI injection of a service that can be mocked
services.AddHttpClient<IMyService,MyService>(client => {
client.BaseAddress = new Uri("https://myservice.com/api");
}); // notice that I use IMyService for the reference of the registration AND implementation to where it will be injected.
}
public class MyService
{
public MyService(HttpClient client)
{
// client.BaseAddress is properly set here
}
}
public class MyController : Controller
{
public MyController(IMyService service) // used by the interface
{}
}
I've tried services.AddHttpClient<IMyService>()
as well, which would not resolve due to lack of it's constructor.
Also tried services.AddHttpClient<MyService>()
as above, but it would not resolve the configured instance, as described above.
So the important part is that class that is used to reference the resolved type needs to be used. So this also works:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<MyService>(); // registering the type itself, not via interface
services.AddHttpClient<MyService>(client => {
client.BaseAddress = new Uri("https://myservice.com/api");
}); // it's ok here, since it will be resolved by it's own type name
}
public class MyService
{
public MyService(HttpClient client)
{
// client.BaseAddress is properly set here
}
}
public class MyController : Controller
{
public MyController(MyService service) // used by the type directly
{}
}
It kind of makes sense, but documentation and examples could be better.
Upvotes: 1
Reputation: 230
I had a similar error message trying to inject a wrapper for an external REST service to my controller as an interface. I needed to change the following in ConfigureServices:
services.AddHttpClient<IMyServiceWrapper>("MyServiceWrapper", client =>
{
client.BaseAddress = new Uri("http://some_service/api");
}
to
services.AddHttpClient<IMyServiceWrapper, MyServiceWrapper>("MyServiceWrapper", client =>
{
client.BaseAddress = new Uri("http://some_service/api");
}
in order to be able to use the interface in the constructor of my controller:
public MyController(IMyServiceWrapper myService)
{
_myService = myService;
}
Useful for testing myController using a mock service.
Upvotes: 4
Reputation: 21779
I was getting a similar error in my Azure Function
Version 2. As per this document, we should be able to add the IHttpClientFactory
as a dependency. After adding this DI
in my Azure Function, I was getting the error mentioned below.
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate 'OServiceBus.Adapter.FetchDataFromSubscription1'
The issue was that I had not override the Configure function to add the HttpClient
as a registered dependency. So I just created a class called Statup
in the root directory of my Azure Function as follows.
using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(ServiceBus.Adapter.Startup))]
namespace ServiceBus.Adapter {
public class Startup: FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.Services.AddHttpClient();
}
}
}
After adding this, my function started working properly. Hope it helps.
Upvotes: 6
Reputation: 93233
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.
Upvotes: 17
Reputation: 46621
It seems that you've got two view components mixed up. You're registering the FixturesViewComponent
as a "named HTTP client" yet you attempt to inject an HttpClient
instance in the ProductsViewComponent
.
Changing the HttpClient registration to ProductsViewComponent
should help:
services.AddHttpClient<ProductsViewComponent>(options =>
{
options.BaseAddress = new Uri("http://80.350.485.118/api/v2");
});
Upvotes: 2