My web app has a background service that listens to a service bus. Based on the docs, it looks like the built-in way to run a background service is to implement IHoste
.net core now has a AddHostedService overload that lets you specify a factory function/method for resolving hosted services. This lets you specify your hosted service as a singleton and then inject it where necessary.
Documentation says it's been available since .Net Core 3.x
Arguably, one should analyze the architecture choices that led to this path to ensure that this route is actually required. But sometimes it may be necessary.
AddHostedService<THostedService>(IServiceCollection, Func<IServiceProvider,THostedService>)
Usage is
services
.AddSingleton<IMyServiceContract, MyServiceImplementation>()
.AddHostedService(services => (MyServiceImplementation) services.GetService<IMyServiceContract>())
;
If your IServiceContract inherits from IHostedService or you don't use an interface then the cast is unnecessary.
None of the complexity in the other answers is needed as of .net core 3.1. If you don't need to get a concrete reference to your class from another class, simply call:
services.AddHostedService<MyHostedServiceType>();
If you must have a concrete reference, do the following:
services.AddSingleton<IHostedService, MyHostedServiceType>();
Based on your answers, I made a helpful extension method. It allows to register an IHostedService
with another interface.
The other interface doesn't need to implement IHostedService
, so you don't expose the StartAsync()
and StopAsync()
methods
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);
}
}
}
Turns out there's an easy way to do this (thanks for the pointer, Steven).
If you need to be able to inject / get a reference to some service, go ahead and register the service normally (without worrying about any IHostedService
stuff):
services.AddSingleton<ServiceBusListener>();
Now we can register a separate hosted service whose only responsibility is to start/stop the service we just registered:
services.AddHostedService<BackgroundServiceStarter<ServiceBusListener>>();
Where BackgroundServiceStarter
is a helper class that looks something like:
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);
}
}
Update 2018/8/6: updated code to avoid service locator pattern thanks to a suggestion from ygoe
Thank you for the answer, Michael, it works well. Strange that we need such hacks to make dependency injection actually work.
I've changed your class so that you don't need the service locator. Generics help here.
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);
}
}
My little helper code in response to the excellent answer of jjxtra
/// <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>());
}
So this will allow you to register your IHostedService with
services
.AddInjectableHostedService<IExposeSomething, MyHostedServiceType>();
Where MyHostedServiceType off course implements some interface (IExposeSomething) with things that you want to expose to other types.