AddHttpClient<T>配置方法未执行,HttpClient依赖注入异常
我原本以为自己了解AddHttpClient的工作原理,但显然并非如此。我已将问题简化到最基础的形式,可它仍未按预期运行。
我定义了如下接口与类:
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; }
该类仅为示例需求暴露BaseAddress属性。
随后我执行了以下测试代码:
[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"); }
测试失败了,因为BaseAddress为null,而非我预期的"http://fakehost"。
原因是SomeService获取到的HttpClient并未经过我的配置方法(我加了断点,但从未触发),不过神奇的是我仍能得到SomeService的实例。
我尝试为接口类型配置HttpClient,但没有效果。我发现如果直接获取SomeService实例,代码能正常运行,但我希望注入的是ISomeService而非具体实现类。
我正在构建一个客户端库,希望消费者仅需调用services.AddSomeService()就能注入ISomeService,请问我遗漏了什么?如何为通过依赖注入的服务(含其他依赖)注入已配置BaseAddress的HttpClient?
编辑:
经测试发现,.AddHttpClient<T>似乎仅在显式解析T时才会为其添加对应的HttpClient,同时还会为其他类添加一个通用HttpClient。注释掉测试中的AddHttpClient部分会导致解析失败,但改为AddHttpClient<SomeOtherClass>后,ISomeService可被解析(但BaseAddress仍为null)。
编辑2:
最初我将ISomeService注册为单例,经检查后发现无需单例模式,改为瞬态后可使用.AddHttpClient<ISomeService, SomeService>(...)。
问题核心在于你拆分了服务注册和HttpClient配置的逻辑,导致DI容器绕开了HttpClient工厂的配置流程:
当你调用
services.AddHttpClient<SomeService>()时,HttpClient工厂只会为直接解析SomeService类型的场景提供配置好的HttpClient。但你额外通过AddSingleton<ISomeService, SomeService>()注册了接口与实现类的映射,此时DI容器会直接创建SomeService实例,并注入默认的未配置HttpClient,完全跳过了工厂的配置逻辑。正确的注册方式是直接使用HttpClient工厂的泛型重载,同时完成接口-实现类映射和HttpClient配置:
// 替换原有的两个注册语句 services.AddHttpClient<ISomeService, SomeService>((sp, client) => { client.BaseAddress = new Uri("http://fakehost"); });
这样注册后,解析ISomeService时,HttpClient工厂会负责创建SomeService实例并注入配置好的HttpClient。
关于单例的注意点:HttpClient工厂创建的服务默认是瞬态的,因为HttpClient背后依赖工厂管理的HttpClientHandler池,若将服务注册为单例会导致HttpClient长期持有,无法利用池化机制,进而引发资源泄漏。你编辑2中改为瞬态的做法是正确的。
封装客户端库的扩展方法:
public static class ServiceCollectionExtensions { public static IHttpClientBuilder AddSomeService(this IServiceCollection services) { return services.AddHttpClient<ISomeService, SomeService>(client => { client.BaseAddress = new Uri("http://fakehost"); }); } }
消费者只需调用services.AddSomeService()即可完成ISomeService的注册和HttpClient配置,返回的IHttpClientBuilder还允许消费者进一步自定义HttpClient的设置。
内容的提问来源于stack exchange,提问作者AceGambit

