Home > OS >  AddHttpClient<T> configureClient method is not executing
AddHttpClient<T> configureClient method is not executing

Time:01-20

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 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