Search code examples
c#dependency-injection.net-core-3.1health-check

.NET Core HealthCheck - Add HealthCheck with dependency injection and parameters


I have different classes which inherit of a base class. The base class implements the interface IHealthCheck. Each class has a constructor which need a logger and parameters according to the class. For example :

public ConnectionHealthCheck(ILogger logger, string address)
        : base(logger)
    {
         Address = address;
    }

I have a appSettings.json which allows me to configure several diagnostics to do in my Health Check service.

I get the list of diagnostics in my App.xaml.cs and i'm trying to add them in the HealthCheck list.

The problem is that I cannot do a dependency injection with parameters next to it and I don't know what is the best solution to do it...

Here is some parts of my code.

The OnStartup method :

protected override void OnStartup(StartupEventArgs e)
    {
        var a = Assembly.GetExecutingAssembly();
        using var stream = a.GetManifestResourceStream("appsettings.json");

        Configuration = new ConfigurationBuilder()
            .AddJsonStream(stream)
            .Build();

        var host = new HostBuilder()
            .ConfigureHostConfiguration(c => c.AddConfiguration(Configuration))
                .ConfigureServices(ConfigureServices)
                .ConfigureLogging(ConfigureLogging)
                .Build();
       [...] }

The configureService Method :

private void ConfigureServices(IServiceCollection serviceCollection)
    {
        // create and add the healthCheck for each diag in the appSettings file
        List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
        diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x)); 
        [...] }

And the method CreateHealthCheck where is the problem :

private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
    {
        EnumDiagType type;

        try
        {
            type = (EnumDiagType)Enum.Parse(typeof(EnumDiagType), configItem.Type, true);
        }
        catch (Exception)
        {
            throw new Exception("Diagnostic type not supported");
        }

        switch (type)
        {
            case EnumDiagType.Connection:
                serviceCollection.AddHealthChecks().AddCheck(nameof(ConnectionHealthCheck), new ConnectionHealthCheck(???, configItem.Value));
                break;
            case EnumDiagType.Other:
                [...] }

As you can see, I cannot create the instance of the ConnectionHealthCheck class because I cannot reach the ILogger object...

So how can I do it ? I think about different solutions but I don't have the answer or the way to do it

  • Build the HealthCheck service not in the App.xaml.cs but after ? (In a view model for exemple where I have access to the serviceCollection and the logger)

  • Find a way to get the logger to use it in the CreateHealthCheck method ?

  • Do something like that but I don't know when I can pass the parameters

    serviceCollection.AddHealthChecks().AddCheck<ConnectionHealthCheck>(nameof(ConnectionHealthCheck));


Solution

  • You can use HealthCheckRegistration to register your class (it should implement IHealthCheck), it has constructors accepting delegate Func<IServiceProvider,IHealthCheck> which allows you to use IServiceProvider to resolve required parameters to create an instance of your healthcheck class. Something like this:

    public static class ConnectionHealthCheckBuilderExtensions
    {
        const string DefaultName = "example_health_check";
    
        public static IHealthChecksBuilder AddConnectionHealthCheck(
            this IHealthChecksBuilder builder,
            string name,
            DiagnosticConfigItem configItem,
            HealthStatus? failureStatus = default,
            IEnumerable<string> tags = default)
        {
            return builder.Add(new HealthCheckRegistration(
                name ?? DefaultName,
                sp => new ConnectionHealthCheck(sp.GetRequiredService<ISomeService>(), configItem.Value),
                failureStatus,
                tags));
        }
    }
    

    See Distribute a health check library section of the docs for more details.

    Another approach is to use the HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck which has following in the remarks:

    This method will use ActivatorUtilities.CreateInstance<T>(IServiceProvider, Object[]) to create the health check instance when needed. Additional arguments can be provided to the constructor via args.

    So the registration of ConnectionHealthCheck will look for example like:

    builder.AddTypeActivatedCheck<ConnectionHealthCheck>(
       name ?? DefaultName, 
       configItem.Value);