我的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
并手动运行后台服务?
答案 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或您不使用接口,则不需要进行强制转换。