简体   繁体   中英

How to inject dependencies inside an ASP.NET Core Health Check

I'm trying to use the new ASP.NET Code 2.2 Healthchecks feature.

In this link on the .net blog, it shows an example:

public void ConfigureServices(IServiceCollection services)
{
    //...
    services
        .AddHealthChecks()
        .AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"]));
    //...
}

public void Configure(IApplicationBuilder app)
{
    app.UseHealthChecks("/healthz");
}

I can add custom checks that implement the Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck interface. But since I need to provide to the AddCheck method an instance instead of a type, and it needs to run inside the ConfigureServices method, I can't inject any dependency in my custom checker.

Is there any way to workaround this?

As of .NET Core 3.0, the registration is simpler and boils down to this

public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
    services.AddSingleton<SomeDependency>();
    services.AddCheck<SomeHealthCheck>("mycheck");
}

Note that you no longer have the singleton vs transient conflict as you use what the engine needs to use.

The name of the check is mandatory, therefore you have to pick up one.

While the accepted asnwer seems no longer to work.

Short Answer

How to inject dependencies inside an ASP.NET Core Health Check.

If we register our services in a correct order, then SomeDependency will be available for injection into the SomeHealthCheck constructor, and SomeHealthCheck will run as part of the health check feature.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
    services.AddSingleton<SomeDependency>();

    // register the custom health check 
    // after AddHealthChecks and after SomeDependency 
    services.AddSingleton<IHealthCheck, SomeHealthCheck>();
}

More Details

A comment in the Health Check samples states that:

All IHealthCheck services will be available to the health check service and middleware. We recommend registering all health checks as Singleton services.

Full Sample

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

public class SomeDependency
{
    public string GetMessage() => "Hello from SomeDependency";
}

public class SomeHealthCheck : IHealthCheck
{
    public string Name => nameof(SomeHealthCheck);

    private readonly SomeDependency someDependency;

    public SomeHealthCheck(SomeDependency someDependency)
    {
        this.someDependency = someDependency;
    }

    public Task<HealthCheckResult> CheckHealthAsync(
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var message = this.someDependency.GetMessage();
        var result = new HealthCheckResult(HealthCheckStatus.Failed, null, null, null);
        return Task.FromResult(result);
    }
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHealthChecks();
        services.AddSingleton<SomeDependency>();
        services.AddSingleton<IHealthCheck, SomeHealthCheck>();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHealthChecks("/healthz");
        app.Run(async (context) => await context.Response.WriteAsync("Hello World!"));
    }
}

This sample is also available on GitHub here .

In addition to Shaun's answer: there is an open pull-request which will allow to inject services with any lifetime (transient and scoped) into health checks. This will probably land in the 2.2 release.

When you can use transient and scoped services in health checks, you should register them using a transient lifestyle .

I was struggling with this in my ASP.NET Core 3.1 Web API as I followed the typical DI approach described above by calling:

services.AddHealthChecks();
services.AddSingleton<IHealthCheck, MyHealthCheck1>();    
services.AddSingleton<IHealthCheck, MyHealthCheck2>();

Unfortunately, it seems in ASP.NET Core 3.1 that does not actually work as my IHealthCheck implementations were not being called.

Instead, I had to do the following in Startup.ConfigureServices():

services.AddHealthChecks()
    .AddCheck<MyHealthCheck1>("My1-check",
        HealthStatus.Unhealthy,
        new string[] { "tag1" })
    .AddCheck<MyHealthCheck2>("My2-check",
        HealthStatus.Unhealthy,
        new string[] { "tag2" });

Then in Startup.Configure(), I also called MapHealthChecks() as follows:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapHealthChecks("/hc");
});

Dependency injection for health checks in asp.net core works exactly as it works for any other registered service that is added through ServiceProvider .

This means creating your health check as

public class Foo : IHealthCheck {
    private ILogger<Foo> _log;
    public Foo(ILogger<Foo> log) {
        _log = log; // log is injected through the DI mechanisms
    }
}

And registering (using the new 6 style here):

builder.AddHealthChecks().AddHealthCheck<Foo>();

So this also means that you can inject the IServiceProvider itself and utilise it internally should the need for getting further required services or werid use cases be there.

I am very curious why this is not explicitly stated in the documentation and there are no examples for this, as it is not "obvious". But it clearly follows the classical pattern of everything in the asp.net core land.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM