I thought I understood how AddHttpClient worked, but apparently, I do not. I've distilled this problem down to the very basics, and it still isn't functioning as I expect.
I have the following class and interface:
public interface ISomeService
{
Uri BaseAddress { get; }
}
public class SomeService : ISomeService
{
private readonly HttpClient _client;
public SomeService(HttpClient client)
{
_client = client;
}
public Uri BaseAddress => _client.BaseAddress;
}
Simply exposing the BaseAddress for the purposes of this example.
Now, I perform the following:
[Fact]
public void TestClient()
{
var services = new ServiceCollection();
services.AddHttpClient<SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
services.AddSingleton<ISomeService, SomeService>();
var provider = services.BuildServiceProvider();
var svc = provider.GetRequiredService<ISomeService>();
svc.BaseAddress.Should().Be("http://fakehost");
}
and it fails, because the base address is null, instead of http://fakehost like I expect.
This is because somehow, SomeService gets an HttpClient created for it without going through my configure method (I added a breakpoint, it never got hit). But like magic I still an actual constructed instance of SomeService.
I've tried adding the HttpClient typed to the interface instead, no luck.
I found that if I GetRequiredService<SomeService>, instead of for ISomeService the code behaves as expected. But I don't want people injecting concrete SomeService. I want them to inject an ISomeService.
What am I missing? How can I inject an HttpClient already configured with a base address to a service that will be, itself, injected via DI (with other potential dependencies as well).
Background: I'm building a client library with ServiceCollectionExtensions so that consumers can just call services.AddSomeService() and it will make ISomeService available for injection.
EDIT:
I have since found (through experimentation) that .AddHttpClient<T> seems to add an HttpClient for T only when explicitly trying to resolve T, but also adds a generic HttpClient for any other class.
commenting out the AddHttpClient section of my test resulted in failed resolution but changing the to AddHttpClient<SomeOtherClass> allowed ISomeService to resolve (still with null BaseAddress though).
EDIT 2:
The example I posted originally said I was registering ISomeService as a singleton, which I was, but after inspection, I've realized I don't need it to be so switching to transient allows me to do
.AddHttpClient<ISomeService, SomeService>(...)
Internally build in DI works with ServiceDescriptors which represent implementation type-service type pairs. AddHttpClient<SomeService> registers SomeService as SomeService which has nothing to do with ISomeService from DI standpoint, just provide service type and implementation type:
services.AddHttpClient<ISomeService, SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
Though it will register the service type as transient.
If you need to have it as singleton - you can try reabstracting (though it should be done with caution as mentioned in the comments):
services.AddHttpClient<SomeService>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("http://fakehost");
});
services.AddSingleton<ISomeService>(sp => sp.GetRequiredService<SomeService>());
Related
In my app I will have few TypedClient services.
However, these classes will share a bunch of methods.
My solution to this is to create CustomHttpClient:
public class CustomHttpClient:HttpClient
{
//here shared methods
}
Then my typed client classes will use this derived class instead of standard HttpClient:
public class MyService : IMyService
{
public SomeService(CustomHttpClient client, IConfiguration configuration){}
}
However, if i try to register this service in startup i get an error that there is no suitable constructor for 'MyService' :
services.AddHttpClient<IMyService, MyService>();
In the documentation I have found:
A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service
Does it mean, that it cannot accept a subclass of HttpClient?
If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods ( i don't really like this solution).
Is there maybe a workaround this, or extension methods are my only way out?
I have tried registering also CustomHttpClient so DI container would find it but the error is still the same.
What can you advise me?
Does it mean, that it cannot accept a subclass of HttpClient?
Yes.
If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods
No. Per the docs typed clients encapsulate an HttpClient, rather than extending it. You configure the HttpClient in the constructor then add custom methods to the Typed Client that use the encapsulated HttpClient instance.
If you don't want to use the framework's pattern for HttpClient handling, you're free to create your own, but it's probably not worth the effort.
You can have typed clients that share a base class. eg
public class MyBaseTypedClient
{
public HttpClient Client { get; }
public MyBaseTypedClient(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
//other methods
}
public class MyTypedClient : MyBaseTypedClient
{
public MyTypedClient(HttpClient client) : base(client) { }
}
If you need to add common methods only you can create an Interface with default implementation of those methods, then you would just need to inherit your IMyService with that interface.
You can also have a look at the below link which has some interesting workarounds.
https://github.com/dotnet/extensions/issues/1988
I think this is what you want to do:
(1) Create your base MyBaseTypedClient as follows:
public interface IMyBaseTypedClient
{
//other methods
//like FetchAsync(), PostAsync()
}
public class MyBaseTypedClient
{
private HttpClient _client
public MyBaseTypedClient(HttpClient client)
{
_client = client;
}
//other methods
//like FetchAsync(), PostAsync()
}
(2) Then create your typed clients as follows:
public interface IServiceOne: IMyBaseTypedClient{ }
public class ServiceOne: MyBaseTypedClient, IServiceOne
{
public ServiceOne(HttpClient httpClient) : base(httpClient)
{
}
}
public interface IServiceTwo: IMyBaseTypedClient{ }
public class ServiceTwo: MyBaseTypedClient, IServiceTwo
{
public ServiceTwo(HttpClient httpClient) : base(httpClient)
{
}
}
(3) Then register as follows
services
.AddHttpClient<IServiceOne, ServiceOne>(c => c.BaseAddress = new Uri("https://serviceone.com"));
.AddHttpClient<IServiceTwo, ServiceTwo>(c => c.BaseAddress = new Uri("https://servicetwo.com");
(4) Then inject as follows
public void SomeMethodThatNeedsOne(IServiceOne serviceOne)
{
//etc
}
public void SomeMethodThatNeedsTwo(IServiceTwo serviceTwo)
{
//etc
}
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.
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>();
TLDR;
ViewComponents 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 ViewComponents. Even with a call to IServiceCollection.AddHttpClient<SomeViewComponent>(), passing an instance of HttpClient into SomeViewComponents 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 ViewComponents: it also applies to Controllers and TagHelpers (thanks to Chris Pratt for confirming for TagHelpers).
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 Controllers and TagHelpers respectively.
If we look at the docs more closely, it's clear that none of the examples there inject a HttpClient into Controllers 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 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.
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.
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");
});
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.
I am having issue when adding a custom client with Refit. I dont want default httpClient. The reason to do is that I dont want my consumers to change httpclient configuration when they are consuming my refit implemented Client. Separation of concerns I guess.
I have a class CustomHttpClient which is inherited from httpClient.
var client = new CustomHttpClient();
serviceCollection.AddRefitClient<IMyAPi>().AddTypedClient(sp => client).ConfigureHttpClient((serviceProvider, httpClient) =>
{
var options = serviceProvider.GetRequiredService<IOptions<CustomClientOptions>>().Value;
httpClient.BaseAddress =
new Uri(
$"{options.ApiProtocol}://{options.ApiHost}/{options.ApiVersion}");
}).AddHttpMessageHandler<CustomHttpClientHandler>();
CustomHttpClient.cs
public class CustomHttpClient : HttpClient
{
public CustomHttpClient(HttpMessageHandler handler) : base(handler)
{
}
public CustomHttpClient()
{
}
}
I am getting exception:
System.InvalidOperationException: 'ValueFactory attempted to access
the Value property of this instance.'
AddTypedClient adds the client as Transient but I want it as Singleton. I am not sure how to tell Refit to use my CustomHttpClient & CustomHttpClientHandler which should be singleton?
Update:
I tried below code and its giving new exception.
serviceCollection.AddSingleton<CustomHttpClient>();
serviceCollection.AddSingleton<CustomHttpClientHandler>();
serviceCollection.AddHttpClient<CustomHttpClient>(c => c.BaseAddress = new Uri("http://dummy.com"));
serviceCollection.AddRefitClient<IMyAPi>().AddHttpMessageHandler<CustomHttpClientHandler>();
System.InvalidOperationException: 'A suitable constructor for type
'BBC.Studios.Rightsline.Client.RightslineHttpClient' could not be
located. Ensure the type is concrete and services are registered for
all parameters of a public constructor.'
I have a business-type service, let's call it AccountService.
class AccountService : IAccountService {
public AccountService(ILoggingService log) {
_log = log;
}
}
As you can see, this service requires a logging service in ctor injection.
The logging service must be instantiated passing the containing service as a type:
var log = LoggingServiceFactory.GetService(typeof(AccountService));
How do I define the object graph so the logging service gets injected as a new instance, passing account service as a type?
I could do this
container.RegisterType<IAccountService, AccountService>();
container.RegisterType<ILoggingService>( /* ??? */);
...but I'm not sure what to put in there.
Do I have to do this?
container.RegisterType<IAccountService>( () => return new AccountService(LoggingServiceFactory.GetService(typeof(AccountService)));
That is OK for this example, but what if my AccountService required other injections as well? Do I have to put everything in that one lambda expression?
container.RegisterType<IAccountService>( () => return new AccountService(LoggingServiceFactory.GetService(typeof(AccountService)), container.Resolve<IOtherService>(), container.Resolve<ISecondOtherService>());
If I do that, what if the other services also require other services (including the logging service again)? Seems like this could get pretty messy pretty fast.
I guess I change things so I could inject the factory instead:
class AccountService : IAccountService {
public AccountService(LoggingServiceFactory factory) {
_log = factory.GetService(typeof(this));
}
}
and register them this way:
container.RegisterType<IAccountService, AccountService>();
container.RegisterType<LoggingServiceFactory, LoggingServiceFactory>();
...but alas there is no ILoggingServiceFactory so I will have to inject a concrete type, which makes it harder to unit test. I cannot change the logging service (it's third party and we have to use it).
Is there a better way?
You can accomplish it in multiple ways:
1) Use factory to get logging service using the reflected class type:
class AccountService : IAccountService
{
public AccountService(ILoggingServiceFactory factory)
{
_log = factory.GetService(this.GetType());
}
}
2) Use factory generic method:
class AccountService : IAccountService
{
public AccountService(ILoggingServiceFactory factory)
{
_log = factory.GetService<AccountService>();
}
}
3) Make logging service generic
class AccountService : IAccountService
{
public AccountService(ILoggingService<AccountService> log)
{
_log = log;
}
}
Depends on what IoC you are using, but most have a way to do this:
container.Register<LoggingService>();
Where you would just get a new instance of LoggingService wherever it was asked for. Most will also have ways to register singleton instances as well. Above is specifically the syntax SimpleInjector uses (it's just what I have sitting in front of me at the moment).
I cannot change the logging service (it's third party and we have to
use it).
I would not use a concrete type unless I absolutely had no choice. Even though you cannot modify it, you can always wrap it in your own concrete class that you can then tie an interface to.
This would allow you to no longer be bound to the concrete class and easily swap the logging service out for another one.
I want to have a service like the following
public SomeService(IMongoDatabase mongoDatabase) {
DB = mongoDatabase;
}
and I want to use a factory to resolve IMongoDatabase, just to encapsulate the IConfiguration usage
public static IMongoDatabase GetMongoDatabase(IConfiguration config)
{
var connectionString = config.Get("SomeConnectionString");
// MongoClient handles connection pooling internally
var client = new MongoClient(connectionString);
var db = client.GetDatabase(config.Get("SomeDbName"));
return db;
}
I can't figure out how to handle the registrations so that MongoDbFactory.GetMongoDatabase gets called whenever any class needs an IMongoDatabase. IConfiguration will be registered already.
I'd really like to just use an IMongoDatabase and not a Func<IConfiguration, IMongoDatabase> in my Service. The latter just seems way too obtuse, requiring consumers to implement steps that I should be able to implement for them.
You can register your static GetMongoDatabase factory method like this :
builder.Register(c => MongoDbFactory.GetMongoDatabase(c.Resolve<IConfiguration>)())
.As<IMongoDatabase>();
By the way, using a static method may introduce some problem, it may be better to register the MongoDbFactory class and use it in your factory registration.
builder.RegisterType<MongoDbFactory>()
.AsSelf();
builder.Register(c => c.Resolve<MongoDbFactory>().GetMongoDatabase())
.As<IMongoDatabase>();
Of course, you will need to adapt the MongoDbFactory implementation to make it work - by adding a property for Configuration and adding IConfiguration to the constructor.