如何在ASP.NET Core Health Check中注入依赖项

23

我正在尝试使用新的ASP.NET Core 2.2 健康检查功能。

在这篇链接中的 .net 博客中,它展示了一个例子:

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方法内运行,所以我无法在自定义检查程序中注入任何依赖项。

有没有办法绕过这个问题?


1
在其中一个示例中,他们使用services.AddSingleton<IHealthCheck, GCInfoHealthCheck>();进行注册。也许这已经在2.2的预览版中起作用了。 - Dirk
5个回答

13

从 .NET Core 3.0 开始,注册就变得更加简单,可以归结为以下内容

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

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

检查的名称是必需的,因此您必须选择一个。

尽管已接受的答案似乎不再起作用。


1
这对我不起作用... services.AddCheck 似乎不存在,示例文档显示我需要调用 services.AddHealthChecks().AddCheck<..>(...)。 对吗? - Patrick Szalapski
你能解释一下这个语句吗:“请注意,您不再需要使用单例与瞬态冲突,因为您使用引擎需要使用的内容。” 你是说 Health Check 类构造函数的参数可以自动提供吗? - jschmitter
@jschmitter 我的意思是:在其他答案中(我相信在健康库的最初版本中也是如此),你被告知要通过 services.AddSingleton<MyCheck>(等等)显式地添加检查。这确实是一个问题,因为除了合同之外,现在依赖关系的类型(Singleton、Scoped、Transient)也是契约性的,你不能自由地更改实现。相反,通过使用 AddCheck 方法,你不需要知道它是 Singleton、Scoped 还是 Transient。这样清楚一点吗? - Yennefer
@PatrickSzalapski 你是否已经包含了所有所需的程序包(并且你使用的是NetCore 6版本)? - Yennefer
1
@PatrickSzalapski 的文档说明为:Services.AddHealthChecks().AddCheck<SomeHealthCheck>("mycheck"); 也许 @Yennefer 有一个自定义的扩展方法? - L01NL
@PatrickSzalapski,AddCheck<T>在程序集Microsoft.Extensions.Diagnostics.HealthChecks中定义,在命名空间Microsoft.Extensions.DependencyInjection中。该项目引用了以下与“健康”相关的包:AspNetCore.HealthChecks.SignalRAspNetCore.HealthChecks.SqlServerAspNetCore.HealthChecks.SystemAspNetCore.HealthChecks.UI.Client。这有帮助吗? - Yennefer

8

简短回答

如何在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>();
}

更多细节

健康检查示例中的评论指出:

所有IHealthCheck服务都将对健康检查服务和中间件可用。我们建议将所有健康检查注册为单例服务。

完整示例

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

这个示例在GitHub上也可以找到


谢谢您提供如此详细的答案!!我有类似的设置,但是我使用的是.AddCheck<MongoDbHealthCheck>("monogcheck", HealthStatus.Unhealthy, new string[] {"monogo"});而不是services.AddSingleton<IHealthCheck, SomeHealthCheck>();。这个设置会抛出异常,因为它无法初始化健康检查。我有点困惑为什么它不起作用?当然,在将其添加为服务之后,它可以正常工作。 - RedRose
3
这是一个很好的答案,但有点过时(请注意下面的Henk的回答)- 现在你可以把任何生命周期的服务注入到健康检查中,并且可以使用类似于services.AddHealthChecks().AddCheck<ApiHealthCheck>("api");这样的语法自动注册你的健康检查。 - k3davis

3

在 asp.net core 中,健康检查的依赖注入与通过 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 core领域中的经典模式。


我可能会向文档提交PR以将其添加到其中,整个晚上都在尝试从文档中理解这是否是应该采取的方式。 - Laazik

3

除了Shaun的回答之外:有一个打开拉请求,它将允许将任何生命周期(瞬态和作用域)的服务注入到健康检查中。这可能会在2.2版本中发布。

当您可以在健康检查中使用瞬态和作用域服务时,应该使用瞬态生命周期进行注册


2

在我的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");
});

那么你基本上无法进行依赖注入吗?这是你的观点吗? - Judy007
@jbooker 我指出了我发现的文档中建议使用 DI 的方法实际上并没有起作用,而我不得不进行调整,如所示。 - BearsEars

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接