如何在 ASP.NET 核心健康检查中注入依赖项

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

我正在尝试使用新的 ASP.NET Code 2.2 Healthchecks 功能。

在 .net 博客上的这个 link 中,它显示了一个示例:

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

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

我可以添加实现 Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck 接口的自定义检查。但是由于我需要向 AddCheck 方法提供一个实例而不是类型,并且它需要 运行 在 ConfigureServices 方法中,所以我无法在我的自定义检查器中注入任何依赖项.

有什么办法可以解决这个问题吗?

简答

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

如果我们以正确的顺序注册我们的服务,那么 SomeDependency 将可用于注入 SomeHealthCheck 构造函数,并且 SomeHealthCheck 将 运行 作为健康检查功能。

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

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

更多详情

A comment in the Health Check samples 指出:

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

完整样本

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

这个样本也是available on GitHub here

除了 Shaun 的回答:还有一个 open pull-request 允许将任何生命周期(瞬态和作用域)的服务注入健康检查。这可能会出现在 2.2 版本中。

当您可以在健康检查中使用瞬态和作用域服务时,您应该register them using a transient lifestyle

从 .NET Core 3.0 开始,注册更简单,归结为这个

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

请注意,当您使用引擎需要使用的内容时,您不再有 singletontransient 冲突。

支票的名称是必填项,因此您必须选择一张。

虽然 似乎不再有效。

我在我的 ASP.NET Core 3.1 Web API 中遇到了这个问题,因为我按照上述典型的 DI 方法调用:

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

不幸的是,似乎在 ASP.NET Core 3.1 中实际上不起作用,因为我的 IHealthCheck 实现没有被调用。

相反,我必须在 Startup.ConfigureServices() 中执行以下操作:

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

然后在Startup.Configure()中,我还调用了MapHealthChecks()如下:

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

asp.net 核心中用于健康检查的依赖注入与它对通过 ServiceProvider.

添加的任何其他注册服务的工作完全相同

这意味着将您的健康检查创建为

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

并注册(此处使用新的 6 样式):

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

因此,这也意味着您可以注入 IServiceProvider 本身,并在需要获得进一步所需的服务或存在其他用例时在内部使用它。

我很好奇为什么文档中没有明确说明这一点,也没有相关示例,因为它不是“显而易见的”。却分明是asp.net核心地万物的经典格局