How do I get a reference to an IHostedService via Dependency Injection in ASP.NET Core?

13,403

Solution 1

There has been some discussion around this topic. For example, see: https://github.com/aspnet/Hosting/issues/1489. One of the problems that you'll run into is that hosted services are added as transient services (from ASP.NET Core 2.1+), meaning that resolving an hosted service from the dependency injection container will result in a new instance each time.

The general advice is to encapsulate any business logic that you want to share with or interact from other services into a specific service. Looking at your code I suggest you implement the business logic in the AbstractProcessQueue<AbstractImportProcess> class and make executing the business logic the only concern of AbstractBackgroundProcessService<T>.

Solution 2

Current workaround from mentioned git page:

services.AddSingleton<YourServiceType>();
services.AddSingleton<IHostedService>(p => p.GetRequiredService<YourServiceType>());

Or, if your service implements some other interfaces:

services.AddSingleton<YourServiceType>();    
services.AddSingleton<IYourServiceType>(p => p.GetRequiredService<YourServiceType>());
services.AddSingleton<IHostedService>(p => p.GetRequiredService<YourServiceType>());

This creates your service as hosted (runs and stops at host's start and shutdown), as well as gets injected as depedency wherever you require it to be.

Update:

I don't see this solution as a "workaround" anymore.

Instead, I would describe it this way: hosted component and a regular service are entities of different types, each one serving its own purpose.

The solution above, however, allows one to combine them, registering a hosted component as a service, which can be used in the dependency resolution chain.

Which is awesome.

Solution 3

This is just a slight modification to the answer by @AgentFire. This method is clearer and allows for several background hosted services in a single Web Service.

services.AddSingleton<YourServiceType>();
services.AddHostedService<YourServiceType>(p => p.GetRequiredService<YourServiceType>());

Solution 4

In .Net Core 3.1 and .Net 5.0you can get references to the existing instances of the Hosted Services with:
IEnumerable<IHostedService> allHostedServices = this.serviceProvider.GetService<IEnumerable<IHostedService>>();

Share:
13,403

Related videos on Youtube

Teun Kooijman
Author by

Teun Kooijman

Updated on June 04, 2022

Comments

  • Teun Kooijman
    Teun Kooijman almost 2 years

    Details

    I have attempted to create a background processing structure using the recommended IHostedService interface in ASP.NET 2.1. I register the services as follows:

    services.AddSingleton<AbstractProcessQueue<AbstractImportProcess>>();
    services.AddHostedService<AbstractBackgroundProcessService<AbstractImportProcess>>();
    
    services.AddSignalR();
    

    The AbstractProcessQueue is just a wrapper around a BlockingCollection of processes that can be enqueued and dequeued. The AbstractBackgroundProcessService implements the IHostedService interface and looks at the queue for new processes it can start.

    Now, the trouble starts when, inside a SignalR hub, I attempt to get a reference to the background processing service via the Dependency Injection mechanisms. I have tried the following solutions, but none seem to be working as intended:

    Option 1:

    public HubImportClient(IServiceProvider provider)
    {
        //This returns null.
        var service = provider.GetService<AbstractBackgroundProcessService<AbstractImportProcess>>();
    }
    

    Option 2:

    public HubImportClient(IServiceProvider provider)
    {
        //This returns null.
        var service = (AbstractBackgroundProcessService<AbstractImportProcess>) provider.GetService(typeof(AbstractBackgroundProcessService<AbstractImportProcess>>));
    }
    

    Option 3:

    public HubImportClient(IServiceProvider provider)
    {
        //This throws an exception, because the service is missing.
        var service = provider.GetRequiredService<AbstractBackgroundProcessService<AbstractImportProcess>>();
    }
    

    Option 4:

    public HubImportClient(IServiceProvider provider)
    {
        //This throws an exception, because the service is missing.
        var service = (AbstractBackgroundProcessService<AbstractImportProcess>) provider.GetRequiredService(typeof(AbstractBackgroundProcessService<AbstractImportProcess>);
    }
    

    Option 5:

    public HubImportClient(IServiceProvider provider)
    {
        //This returns a correct service, but prevents me from adding additional AbstractBackgroundProcessService implementations with different type parameters.
        //Additionally, it seems like this reference was newly created, and not the instance that was created on application startup (i.e. the hash codes are different, and the constructor is called an additional time).
        var service = provider.GetService<IHostedService>();
        if(service is AbstractBackgroundProcessService<AbstractProcessService>)
        {    this.Service = (AbstractBackgroundProcessService<AbstractProcessService>) service;}
    }
    

    Option 6:

    public HubImportClient(IServiceProvider provider)
    {
        //This works similarly to the previous option, and allows multiple implementations, but the constructor is still called twice and the instances thus differ.
        AbstractBackgroundProcessService<AbstractImportProcess> service = null;
        foreach(IHostedService service in provider.GetServices<IHostedService>())
        {
            if(service is AbstractBackgroundProcessService<AbstractImportProcess>)
            {
                service = (AbstractBackgroundProcessService<AbstractImportProcess>) service;
                break;
            }
        }  
    }
    

    Option 7:

    public HubImportClient(IServiceProvider provider)
    {
        //This just skips the for each loop all together, because no such services could be found.
        AbstractBackgroundProcessService<AbstractImportProcess> service = null;
        foreach(AbstractBackgroundProcessService<AbstractImportProcess> current in provider.GetServices<AbstractBackgroundProcessService<AbstractImportProcess> >())
        {
            service = current;
            break;
        }    
    }
    

    Option 8:

    //This works, but prevents multiple implementations again.
    public HubImportClient(IHostedService service)
    {
        this.Service = service;   
    }
    

    Option 9:

    //This does not work again.
    public HubImportClient(AbstractBackgroundProcessService<AbstractImportProcess> service)
    {
        this.Service = service;   
    }
    

    Question

    So then my question remains: how am I supposed to get a reference to an IHostedService implementation so that:

    (a): I can inject multiple instances of the service that differ only by their type parameter (e.g. a hosted service for AbstractImportProcesses as well as one for AbstractExportProcesses)

    (b): there is only ever one instance of the IHostedService for that specific type parameter.

    Thanks in advance for any help!

    • Chris Pratt
      Chris Pratt over 5 years
      Why do you need the servince instance directly? This is atypical. All you should need to do is merely register the IHostedService implementation. ASP.NET Core takes care of instantiating and running it.
    • Chris Marisic
      Chris Marisic over 5 years
      @ChrisPratt i expect to acquire the state of the configured active instance of my HostedService, that it would be the entire purpose of having Start/Stop, that my scoped services receive the hot instance bootstrapped by my HostedService.
    • Chris Marisic
      Chris Marisic over 5 years
      Ultimately i just added a static property to my HostedService to register against in DI.
  • Teun Kooijman
    Teun Kooijman over 5 years
    Thanks for your answer. Your advice, combined with Chris Pratt's comment, have made me decide to move the business logic to the process queue and leave the hosted service for what it is. I don't quite understand why they have opted to add the hosted services as a transient dependency, but this solution will do for now. Thanks alot for the help!
  • Chris Marisic
    Chris Marisic over 5 years
    That thread over there is really strange, i hope everyone here comments on it.
  • too
    too over 3 years
    Is there a better way available in dotnet 5?
  • Losbaltica
    Losbaltica over 3 years
    Not sure how it works but when I run _serviceProvider.GetService<IHostedService>(); I get my hosted service type back. Not sure how it will work with multiple hosted services.
  • AgentFire
    AgentFire about 3 years
    @too No, and I don't think you could prove that some one-liner would be "better" for any reason except for being a one-liner.
  • Thomas
    Thomas almost 3 years
    Thank you @AgentFire ;) saved my day