Search code examples
c#dotnet-httpclienthealth-monitoring

Build a typed HttpClient with AddHealthCheck on same class


I have a http's device that the application contact.

In first step, I have build around the IHttpFactory as preconised by Microsoft

        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"]);
        });  

This is working well, extensible with Polly and Logger and used with simple function

        public async Task SendOpenP1Async() => await httpClient.GetAsync($"?E={telecommande.Emetteur}&T={telecommande.OpenP1}");

In next step, I have implemented health monitoring on this device like this

    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        using var httpClient = _httpClientFactory.CreateClient();
        httpClient.SetBasicAuthentication(synchronic.User, synchronic.Pass);

        var response = await httpClient.GetAsync($"{synchronic.Protocol}://{synchronic.Host}:{synchronic.Port}?E={telecommande.Emetteur}&T={telecommande.Status}", cancellationToken);

        return response.IsSuccessStatusCode ?
                        HealthCheckResult.Healthy($"Synchronic device is healthly") :
                        HealthCheckResult.Unhealthy("Synchronic device is unhealthy");
    }

and add the feature on Program.cs by this way

        builder.Services.AddHealthChecks()
                        .AddCheck<SynchronicHealthCheck>("Synchronic Health Check", failureStatus: HealthStatus.Unhealthy);

this is working like a charm

BUT

if I declare interfaces on same class like this

public class SynchronicProvider : ISynchronicProvider, IHealthCheck

and implement with injected HttpClient instead IHttpClientFactory

    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 base url is empty for this method, but ever populated on another methods.

In Program.cs, I have tried to declare health feature before and after

        // Http factory
        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"]);
        });

        // Health check
        builder.Services.AddHealthChecks()
                        .AddCheck<SynchronicProvider>("Synchronic Health Check", failureStatus: HealthStatus.Unhealthy);

So, why it is not working ? I guess that dependancy injection from AddHttpClient that "take the lead", but I don't know not more.

Can you explain this, and / or resolve the issue ?

Thanks for helping


Solution

  • In response of this use case, you can inherit interface IHealthCheck on IProvider and declare IProvider for HealthCheck binding. Then, in implementation class, you have the both implementation and especially a well configured HttpClient.

    //interface declaration
            public interface ISynchronicProvider : IHealthCheck { ... } 
    
    // binding HealthCheck on interface
            builder.Services.AddHealthChecks()
                            .AddCheck<ISynchronicProvider>("Synchronic Health Check", failureStatus: HealthStatus.Unhealthy); 
    
    // httpClient is working in both impl.
            public async Task SendStatusAsync() => await httpClient.GetAsync($"?E={telecommande.Emetteur}&T={telecommande.Status}");
    
            // https://stackoverflow.com/questions/78794475/build-a-typed-httpclient-with-addhealthcheck-on-same-class
            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");
            }