Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject a reference to a specific IHostedService implementation?

Tags:

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 IHostedService.

So I have some code that looks like this:

public class ServiceBusListener : IMessageSource<string>, IHostedService {     public virtual event ServiceBusMessageHandler<string> OnMessage = delegate { };      public Task StartAsync(CancellationToken cancellationToken)     {         // run the background task...     }      // ... other stuff ... } 

The service is then registered in Startup.cs with:

services.AddSingleton<IHostedService, ServiceBusListener>(); 

Once I update to ASP.NET 2.1 I can use the new convenience method:

services.AddHostedService<ServiceBusListener>(); 

But I believe the two are functionally equivalent.

The complication: my web app has multiple implementations of IHostedService (specifically, different instances of service bus listeners).

The question: how can I have some other component get a reference to a specific hosted service implementation (my service bus listener)? In other words, how do I get a specific instance injected into a component?

Use case: my background service listens for service bus messages and then re-publishes messages as .NET events (in case you're wondering, the consuming code deals with the threading issues). If the event is on the background service, then subscribers need to get a reference to the background service to be able to subscribe.

What I've tried: if I do the obvious thing and declare ServiceBusListener as a dependency to be injected into a different component, my startup code throws a "Could not resolve a service of type" exception.

Is it even possible to request a specific implementation of a IHostedService? If not, what's the best workaround? Introduce a third component that both my service and the consumer can reference? Avoid IHostedService and run the background service manually?

like image 774
Michael Kropat Avatar asked Jul 09 '18 21:07

Michael Kropat


2 Answers

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

like image 116
Michael Kropat Avatar answered Sep 19 '22 12:09

Michael Kropat


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);         }     } } 
like image 38
sroll Avatar answered Sep 17 '22 12:09

sroll