24

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?

Michael Kropat
  • 14,557
  • 12
  • 70
  • 91
  • Use factories methods and resolve the depdendencies into your class and new it within the factory method. may get tedious when it has many constructor parameters. Alternatively use a 3rd party resolver. Last but not least you can use the ActivatorUtils.CreateInstance method to replace a dependency with a fitting object (which you need resolve some where or new it inside the factory method (depends on your case which one suites most). https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.activatorutilities.createinstance?view=aspnetcore-2.1 – Tseng Jul 09 '18 at 21:48
  • Also _"Could not resolve a service of type" exception_ that's because you didn't register it :P You only have registration of IHostedService => ServiceBusListener, not ServiceBusListener => ServiceBusListener – Tseng Jul 09 '18 at 22:25
  • Why would you want to get a reference to a hosted service? Hosted services are meant to run in the background on their own. Maybe you just need to create an interface and register a singleton which you will then be able to inject wherever you need it? – marcusturewicz Jul 10 '18 at 00:59
  • Related: https://stackoverflow.com/questions/50394666/injecting-simple-injector-components-into-ihostedservice-with-asp-net-core-2-0 – Steven Jul 10 '18 at 10:27
  • @Steven: I like that idea. Maybe have a startup class that implements IHostedService that knows how to delegate to a singleton (with my ideal interface) that I register separately. – Michael Kropat Jul 10 '18 at 13:29

6 Answers6

39

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

Michael Kropat
  • 14,557
  • 12
  • 70
  • 91
16

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);
        }
    }
}
sroll
  • 752
  • 7
  • 20
10

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);
    }
}
iCodeSometime
  • 1,444
  • 15
  • 30
ygoe
  • 18,655
  • 23
  • 113
  • 210
8

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

MDR
  • 271
  • 2
  • 7
  • This solution worked for me and seems like the most succinct option. However I found it better to use services.GetRequiredService to avoid a compilation warning about nullability. GetRequiredService() is declared in namespace Microsoft.Extensions.DependencyInjection class ServiceProviderServiceExtensions. – camelCase Feb 27 '23 at 12:00
  • What is `IMyServiceContract` supposed to be? Do you have to have an interface that is dedicated to your service? My services just directly implement `IHostedService`. How do I use your example code in that case? – Jez Feb 27 '23 at 14:55
  • @Jez IMyServiceContract is simply an interface that defines the contract for MyServiceContract. It is used primarily because recommended practice for service locator registration/injection such as this says you seldom want to register your implementation under the actual concrete implementation. You could in theory replace MyServiceContract with IMyServiceContract if you wanted to. – MDR Feb 28 '23 at 13:57
5

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>();
jjxtra
  • 20,415
  • 16
  • 100
  • 140
  • Do you mean we should call AddSingleton and AddHostedService? If so, in what order? – Benni Nov 20 '20 at 23:50
  • I noticed that `services.AddSingleton` also works. Not sure what happens if you add multiple hosted services, though – Benni Nov 22 '20 at 22:46
  • The code I shared is only needed if you need to get a reference to the service from another service. If you don't need that, `services.AddHostedService` is sufficient by itself. – jjxtra Nov 23 '20 at 02:13
  • `services.AddSingleton` allows you to get a reference to the service from another service – Benni Nov 23 '20 at 10:34
  • 1
    I got two instances. Try set some thing in `ExecuteAsync`/`StartAsync`, and when use `IHostedService` that "some thing" not found – Trương Quốc Khánh Nov 03 '21 at 04:09
  • You have to do one or the other, not both. – jjxtra Nov 03 '21 at 15:22
3

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.

Sunib
  • 304
  • 2
  • 14