如何注入对特定IHostedService实现的引用?

时间:2018-07-09 21:08:19

标签: asp.net-core dependency-injection

我的Web应用程序具有监听服务总线的后台服务。基于docs,运行后台服务的内置方法似乎是实现IHostedService

所以我有一些看起来像这样的代码:

public class ServiceBusListener : IMessageSource<string>, IHostedService
{
    public virtual event ServiceBusMessageHandler<string> OnMessage = delegate { };

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // run the background task...
    }

    // ... other stuff ...
}

该服务随后在Startup.cs中注册:

services.AddSingleton<IHostedService, ServiceBusListener>();

一旦我更新到ASP.NET 2.1,就可以使用新的便捷方法:

services.AddHostedService<ServiceBusListener>();

但是我相信两者在功能上是等效的。

复杂之处::我的Web应用程序具有IHostedService的多种实现(特别是服务总线侦听器的不同实例)。

问题:我如何让其他组件获得对特定托管服务实现(我的服务总线侦听器)的引用?换句话说,如何将特定实例注入到组件中?

用例::我的后台服务侦听服务总线消息,然后将消息重新发布为.NET事件(如果您想知道,使用代码处理线程问题)。如果事件在后台服务上,则订阅者需要获取对后台服务的引用才能进行订阅。

我尝试过的操作:如果我做了显而易见的事情,并将ServiceBusListener声明为要注入到其他组件中的依赖项,则我的启动代码将引发“无法解析服务类型”例外。

是否有可能要求IHostedService的特定实现?如果没有,最好的解决方法是什么?引入我的服务和消费者都可以参考的第三个组件?避免使用IHostedService并手动运行后台服务?

6 个答案:

答案 0 :(得分:13)

结果证明有一种简单的方法(感谢指针Steven)。

如果您需要能够注入/获得对某项服务的引用,请继续正常注册该服务(无需担心任何IHostedService东西)

services.AddSingleton<ServiceBusListener>();

现在我们可以注册一个单独的托管服务,其唯一责任是启动/停止我们刚刚注册的服务:

services.AddHostedService<BackgroundServiceStarter<ServiceBusListener>>();

BackgroundServiceStarter是一个类似于以下内容的帮助程序类:

public class BackgroundServiceStarter<T> : IHostedService where T:IHostedService
{
    readonly T backgroundService;

    public BackgroundServiceStarter(T backgroundService)
    {
        this.backgroundService = backgroundService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StartAsync(cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StopAsync(cancellationToken);
    }
}

更新2018/8/6 :由于ygoe

的建议,更新了代码以避免服务定位器模式

答案 1 :(得分:5)

谢谢您的回答,迈克尔,效果很好。奇怪的是,我们需要这样的技巧才能使依赖注入真正起作用。

我更改了班级,因此您不需要服务定位器。泛型在这里有帮助。

public class BackgroundServiceStarter<T> : IHostedService
    where T : IHostedService
{
    private readonly T backgroundService;

    public BackgroundServiceStarter(T backgroundService)
    {
        this.backgroundService = backgroundService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StartAsync(cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StopAsync(cancellationToken);
    }
}

答案 2 :(得分:4)

根据您的回答,我提出了一种有用的扩展方法。它允许在另一个接口中注册IHostedService

另一个接口不需要实现IHostedService,因此您无需公开StartAsync()StopAsync()方法

public static class ServiceCollectionUtils
{
    public static void AddHostedService<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, IHostedService, TService
    {
        services.AddSingleton<TService, TImplementation>();
        services.AddHostedService<HostedServiceWrapper<TService>>();
    }

    private class HostedServiceWrapper<TService> : IHostedService
    {
        private readonly IHostedService _hostedService;

        public HostedServiceWrapper(TService hostedService)
        {
            _hostedService = (IHostedService)hostedService;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            return _hostedService.StartAsync(cancellationToken);
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return _hostedService.StopAsync(cancellationToken);
        }
    }
}

答案 3 :(得分:2)

从.net core 3.1开始,其他答案都不需要复杂性。如果您不需要从另一个类中获得对您的类的具体引用,只需调用:

services.AddHostedService<MyHostedServiceType>();

如果您必须有具体的参考,请执行以下操作:

services.AddSingleton<IHostedService, MyHostedServiceType>();

答案 4 :(得分:0)

我的小助手代码响应jjxtra的excellent answer

    /// <summary>
    /// Instead of calling service.AddHostedService<T> you call this make sure that you can also access the hosted service by interface TImplementation
    /// https://stackoverflow.com/a/64689263/619465
    /// </summary>
    /// <param name="services">The service collection</param>
    public static void AddInjectableHostedService<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, IHostedService, TService
    {
        services.AddSingleton<TImplementation>();
        services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<TImplementation>());
        services.AddSingleton<TService>(provider => provider.GetRequiredService<TImplementation>());
    }

因此,这将允许您使用以下方式注册IHostedService:

        services
            .AddInjectableHostedService<IExposeSomething, MyHostedServiceType>();

MyHostedServiceType当然会实现与您要公开给其他类型的东西的某种接口(IExposeSomething)。

答案 5 :(得分:0)

.net核心现在具有AddHostedService重载,可让您指定用于解析托管服务的工厂功能/方法。这样,您就可以将托管服务指定为单例,然后在必要时将其注入。

文档说该文档自.Net Core 3.x

开始可用

可以说,应该分析导致这种情况的体系结构选择,以确保确实需要这种方法。但有时可能有必要。

AddHostedService<THostedService>(IServiceCollection, Func<IServiceProvider,THostedService>)

用法是

services
   .AddSingleton<IMyServiceContract, MyServiceImplementation>()
   .AddHostedService(services => (MyServiceImplementation) services.GetService<IMyServiceContract>())
;

如果您的IServiceContract继承自IHostedService或您不使用接口,则不需要进行强制转换。