如何在控制器中解决HostedService问题

42
我正在尝试在ASP.NET Core 3.0中添加后台计时器,以定期执行任务。谷歌搜索引导我到了 这里,我实现了 "定时后台任务" 。但是,我在解决控制器中的 HostedService 时遇到了困难。我需要一个特定的 TimedHealthCheckService 实例,以便我可以调用另一个名为“GetAvailableODataUrl()”的公共函数。

startup.cs 中,我使用以下代码:

services.AddHostedService<TimedHealthCheckService>();

TimedHealthCheckService显然实现了IHostedService接口:

public class TimedHealthCheckService : IHostedService, IDisposable                  

在我的控制器中,我有以下构造函数:

public HealthCheckController(ILogger<HealthCheckController> logger, IHostedService hostedService)
{
  this.logger = logger;
  this.timedHealthCheckService = hostedService as TimedHealthCheckService;
}

然而,当我启动我的WebAPI时,timedHealthCheckService 总是为空。似乎另一个 IHostedService 在构造函数中被注入了进来。通过检查 hostedService,它实际上是 GenericWebHostService 类型的一个对象。

如果我将控制器的构造函数改为:

public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)

我遇到了以下错误:

无法解析服务类型 'HealthCheck.Web.TimedHealthCheckService',尝试激活 'HealthCheck.Web.Controllers.HealthCheckController' 时发生错误。

我还尝试过 services.AddSingleton<IHostedService, TimedHealthCheckService>(); 但结果相同。


你为什么需要注入托管服务?这可能是一个XY问题 - Nkosi
托管服务由框架自动启动,因此我正在尝试理解为什么您需要将服务实例注入控制器。 - Nkosi
@Nkosi 这个 HostedService 将保存实际的健康状态。因此,我希望能够直接从 HostedService 中获取该状态。 - Fabian Bigler
我建议改变设计。有一个中间服务,可以注入到控制器和托管服务中。将该服务设置为单例,以便状态可以在它们之间共享。 - Nkosi
@Nkosi 我想这是一个可行的选择,谢谢。然而,我仍然想知道为什么IHostedService不能像预期的那样被注入。这是设计上的原因吗? - Fabian Bigler
有很多东西实现了IHostedService接口。当你请求一个IHostedService时,框架不知道该给你哪个。所以它会选择一个(我不知道是如何选择的)并将其提供给你。 - Jeffrey Rennie
1个回答

92

在 startup.cs 中尝试使用以下两行代码:

services.AddSingleton<TimedHealthCheckService>();
services.AddHostedService<TimedHealthCheckService>(provider => provider.GetService<TimedHealthCheckService>());

以上的第一行代码告诉服务提供商创建一个单例并将其提供给任何想要 TimedHealthCheckService 的人,例如您的控制器构造函数。然而,服务提供商不知道这个单例实际上是一个 IHostedService,并且您希望它调用 StartAsync()

第二行代码告诉服务提供商您要添加一个托管的服务,因此在应用程序开始运行时会调用 StartAsync()AddHostedService 接受一个 Func<IServiceProvider,THostedService> 回调函数。我们提供的回调函数从服务提供商中获取单例 TimedHealthCheckService 并将其作为 IHostedService 返回给服务提供商。然后服务提供商调用它的 StartAsync() 函数。

在您的控制器中:

public HealthCheckController(ILogger<HealthCheckController> logger, TimedHealthCheckService hostedService)

太好了,现在它可以工作了,谢谢。您能详细说明为什么需要IServiceProvider吗? - Fabian Bigler
3
在 ASP.NET Core 2.1 中,AddHostedService 方法不接受参数。有什么建议可以绕过这个限制吗? - Nissim
你好 @jeffrey Rennie,请问如何在XUNIT中模拟TimedHealthCheckService? - Abdellah Azizi
2
太好了!我也想知道为什么我的类构造函数被调用两次,即使我将其添加为“Singleton”。因此,“Singleton”实例除非我遵循这个答案,否则将被用于HostedSerivce。 - F. Kam

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