Elsassmania
Elsassmania

Reputation: 175

.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

Upvotes: 8

Views: 9421

Answers (2)

Guru Stron
Guru Stron

Reputation: 141720

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

Upvotes: 18

Sathish Guru V
Sathish Guru V

Reputation: 1479

The .NET Core in-built DI can inject the components on the Constructor level. So use the following way, which I use in my ASP.NET Core Projects.

public class Startup
{
    public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        Environment = environment;
        Configuration = configuration;
        LoggerFactory = loggerFactory;
    }

    public IConfiguration Configuration { get; }
    public ILoggerFactory LoggerFactory { get; }
    public IWebHostEnvironment Environment { get; }
    
    private void ConfigureServices(IServiceCollection serviceCollection)
    {
        List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
        diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x, LoggerFactory)); 
    }
    private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
    {
        // Create a ILogger<T> based on your Type by
        loggerFactory.CreateLogger<MessagingServices>())
    }
}
    

This might be crude, but hope this helps.

Upvotes: -2

Related Questions