同一类实现带AddHealthCheck的类型化HttpClient遇BaseAddress为空问题
问题场景
我开发的应用需要对接一个HTTP设备,按照微软推荐的方式基于IHttpFactory构建了类型化HttpClient:
builder.Services.AddHttpClient<ISynchronicProvider, SynchronicProvider>(client => { client.BaseAddress = new Uri($"{builder.Configuration["Synchronic:protocol"]}://{builder.Configuration["Synchronic:host"]}:{builder.Configuration["Synchronic:port"]}"); client.SetBasicAuthentication(builder.Configuration["Synchronic:user"], builder.Configuration["Synchronic:pass"]); });
该实现支持扩展Polly、日志功能,调用SendOpenP1Async方法完全正常。
之后我单独实现健康检查逻辑,通过IHttpClientFactory创建HttpClient调用设备状态接口,注册SynchronicHealthCheck后运行正常。
但当我让SynchronicProvider同时实现ISynchronicProvider和IHealthCheck接口,改用注入的HttpClient实现健康检查方法时:
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { var response = await httpClient.GetAsync($"?E={telecommande.Emetteur}&T={telecommande.Status}", cancellationToken); return response.IsSuccessStatusCode ? HealthCheckResult.Healthy("Synchronic device is healthly") : HealthCheckResult.Unhealthy("Synchronic device is unhealthy"); }
发现健康检查方法中的HttpClient的BaseAddress为空,但其他方法里的BaseAddress是正常的。调整AddHttpClient和AddHealthChecks的注册顺序后,问题依然存在。
问题原因
这是因为.NET健康检查系统在实例化IHealthCheck实现类时,没有使用由AddHttpClient注册的类型化客户端实例,而是直接从DI容器中解析了一个全新的SynchronicProvider实例——这个实例并未关联到配置好的类型化HttpClient,所以注入的HttpClient是默认的、未配置BaseAddress的实例。
具体细节:
- 通过
AddHttpClient<ISynchronicProvider, SynchronicProvider>注册时,DI容器中会生成两个注册项:一个是ISynchronicProvider指向由HttpClientFactory包装的SynchronicProvider实例(带配置好的HttpClient);另一个是原始的SynchronicProvider类型注册(不带配置的HttpClient)。 - 健康检查系统解析
IHealthCheck时,会直接请求SynchronicProvider类型的实例,而非通过ISynchronicProvider接口,因此拿到的是未被HttpClientFactory处理过的实例,导致注入的HttpClient缺少BaseAddress配置。
解决方法
方法一:注册健康检查时指定通过接口解析实例
在注册健康检查时,明确要求DI容器通过ISynchronicProvider接口获取实例,这样就能拿到带有配置好的HttpClient的实例:
builder.Services.AddHealthChecks() .AddCheck<ISynchronicProvider>("synchronic_health_check");
方法二:手动关联类型化客户端到健康检查实例
如果必须直接使用SynchronicProvider类型注册健康检查,可以通过工厂方法从DI容器中获取正确的实例:
builder.Services.AddHealthChecks() .AddCheck("synchronic_health_check", sp => sp.GetRequiredService<ISynchronicProvider>() as IHealthCheck);
方法三:确保类型化客户端实例被全局复用
移除SynchronicProvider的单独注册(如果存在),确保DI容器中只有AddHttpClient注册的实例,再通过接口注册健康检查:
// 仅保留AddHttpClient的注册,不要单独注册SynchronicProvider builder.Services.AddHttpClient<ISynchronicProvider, SynchronicProvider>(client => { client.BaseAddress = new Uri($"{builder.Configuration["Synchronic:protocol"]}://{builder.Configuration["Synchronic:host"]}:{builder.Configuration["Synchronic:port"]}"); client.SetBasicAuthentication(builder.Configuration["Synchronic:user"], builder.Configuration["Synchronic:pass"]); }); // 通过接口注册健康检查 builder.Services.AddHealthChecks() .AddCheck<ISynchronicProvider>("synchronic_health_check");
内容的提问来源于stack exchange,提问作者User.Anonymous

