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>(...)
CodePudding user response:
Internally build in DI works with ServiceDescriptor
s 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>());