如何通过 ASP.NET Core 中的依赖注入获取对 IHostedService 的引用?

问题描述 投票:0回答:6

详情

我尝试使用 ASP.NET 2.1 中推荐的

IHostedService
接口创建后台处理结构。我注册服务如下:

services.AddSingleton<AbstractProcessQueue<AbstractImportProcess>>();
services.AddHostedService<AbstractBackgroundProcessService<AbstractImportProcess>>();

services.AddSignalR();

AbstractProcessQueue
只是可入队和出队的
BlockingCollection
进程的包装。
AbstractBackgroundProcessService
实现
IHostedService
接口并查看队列以查找可以启动的新进程。

现在,当我在

SignalR
集线器内尝试通过
Dependency Injection
机制获取对后台处理服务的引用时,麻烦就开始了。我已经尝试了以下解决方案,但似乎没有一个能按预期工作:

选项 1:

public HubImportClient(IServiceProvider provider)
{
    //This returns null.
    var service = provider.GetService<AbstractBackgroundProcessService<AbstractImportProcess>>();
}

选项2:

public HubImportClient(IServiceProvider provider)
{
    //This returns null.
    var service = (AbstractBackgroundProcessService<AbstractImportProcess>) provider.GetService(typeof(AbstractBackgroundProcessService<AbstractImportProcess>>));
}

选项 3:

public HubImportClient(IServiceProvider provider)
{
    //This throws an exception, because the service is missing.
    var service = provider.GetRequiredService<AbstractBackgroundProcessService<AbstractImportProcess>>();
}

选项 4:

public HubImportClient(IServiceProvider provider)
{
    //This throws an exception, because the service is missing.
    var service = (AbstractBackgroundProcessService<AbstractImportProcess>) provider.GetRequiredService(typeof(AbstractBackgroundProcessService<AbstractImportProcess>);
}

选项5:

public HubImportClient(IServiceProvider provider)
{
    //This returns a correct service, but prevents me from adding additional AbstractBackgroundProcessService implementations with different type parameters.
    //Additionally, it seems like this reference was newly created, and not the instance that was created on application startup (i.e. the hash codes are different, and the constructor is called an additional time).
    var service = provider.GetService<IHostedService>();
    if(service is AbstractBackgroundProcessService<AbstractProcessService>)
    {    this.Service = (AbstractBackgroundProcessService<AbstractProcessService>) service;}
}

选项 6:

public HubImportClient(IServiceProvider provider)
{
    //This works similarly to the previous option, and allows multiple implementations, but the constructor is still called twice and the instances thus differ.
    AbstractBackgroundProcessService<AbstractImportProcess> service = null;
    foreach(IHostedService service in provider.GetServices<IHostedService>())
    {
        if(service is AbstractBackgroundProcessService<AbstractImportProcess>)
        {
            service = (AbstractBackgroundProcessService<AbstractImportProcess>) service;
            break;
        }
    }  
}

选项7:

public HubImportClient(IServiceProvider provider)
{
    //This just skips the for each loop all together, because no such services could be found.
    AbstractBackgroundProcessService<AbstractImportProcess> service = null;
    foreach(AbstractBackgroundProcessService<AbstractImportProcess> current in provider.GetServices<AbstractBackgroundProcessService<AbstractImportProcess> >())
    {
        service = current;
        break;
    }    
}

选项8:

//This works, but prevents multiple implementations again.
public HubImportClient(IHostedService service)
{
    this.Service = service;   
}

选项9:

//This does not work again.
public HubImportClient(AbstractBackgroundProcessService<AbstractImportProcess> service)
{
    this.Service = service;   
}

问题

那么我的问题仍然是:我应该如何获得对

IHostedService
实现的引用,以便:

(a):我可以注入仅类型参数不同的服务的多个实例(例如

AbstractImportProcess
es 的托管服务以及
AbstractExportProcess
es 的托管服务)

(b):该特定类型参数只有一个

IHostedService
实例。

预先感谢您的帮助!

c# asp.net asp.net-core dependency-injection asp.net-core-2.1
6个回答
61
投票

提到的 git 页面的当前解决方法:

services.AddSingleton<YourServiceType>();
services.AddSingleton<IHostedService>(p => p.GetRequiredService<YourServiceType>());

或者,如果您的服务实现了一些其他接口:

services.AddSingleton<YourServiceType>();    
services.AddSingleton<IYourServiceType>(p => p.GetRequiredService<YourServiceType>());
services.AddSingleton<IHostedService>(p => p.GetRequiredService<YourServiceType>());

这会将您的服务创建为托管服务(在主机启动和关闭时运行和停止),并在您需要的任何地方作为依赖项注入。

更新:

我不再将此解决方案视为“解决方法”。

相反,我会这样描述它:托管组件和常规服务是不同类型的实体,每个实体都有自己的用途。

但是,上面的解决方案允许将它们组合起来,将托管组件注册为一项服务,可以在依赖项解析链中使用。

这太棒了。


30
投票

这只是对@AgentFire 答案的轻微修改。这种方法更清晰,并且允许在单个 Web 服务中运行多个后台托管服务。

services.AddSingleton<YourServiceType>();
services.AddHostedService<YourServiceType>(p => p.GetRequiredService<YourServiceType>());

11
投票

围绕这个话题已经有一些讨论。例如,请参阅:https://github.com/aspnet/Hosting/issues/1489。您将遇到的问题之一是托管服务被添加为临时服务(从 ASP.NET Core 2.1+ 开始),这意味着从依赖项注入容器解析托管服务每次都会生成一个新实例。

一般建议是将您想要与其他服务共享或交互的任何业务逻辑封装到特定服务中。看看你的代码,我建议你在

AbstractProcessQueue<AbstractImportProcess>
类中实现业务逻辑,并使执行业务逻辑成为
AbstractBackgroundProcessService<T>
唯一关心的问题。


5
投票

您可以直接从 dotnet core 3.1 及更高版本中的

IServiceProvider
获取此内容:

var myHostedService = serviceProvider
    .GetServices<IHostedService>()
    .OfType<MyHostedService>()
    .Single();

5
投票

这是基于@andyvan 和@Hursev 答案的完整解决方案。截至 2022 年,与公认的和最流行的答案相比,似乎是更“当前”和简洁的解决方法。

//regular implementation of Background/IHostedService. Nothing special.
public class MyBackgroundService : BackgroundService
{
    
}

//Startup.cs or Program.cs
//Also standard. No extra code required.
services.AddHostedService<MyBackgroundService>();
services.AddTransient<IMyService, MyService>();


//MyService is where we want to consume background servive
public class MyService : IMyService
{
    private readonly MyBackgroundService _service;

    public MyService(IServiceProvider serviceProvider)
    {
        _service = serviceProvider.GetServices<IHostedService>().OfType<MyBackgroundService>().Single();
    }

    public void DoStuff()
    {
        //Consume background service as any other DI services.
        _service.SomethingFromMyBackgroundService();
    }
}

3
投票

在 .Net Core 3.1 和 .Net 5.0 中,您可以通过以下方式获取对托管服务现有实例的引用:

IEnumerable<IHostedService> allHostedServices = this.serviceProvider.GetService<IEnumerable<IHostedService>>();

© www.soinside.com 2019 - 2024. All rights reserved.