Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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!

like image 621
Teun Kooijman Avatar asked Aug 27 '18 10:08

Teun Kooijman


People also ask

How can we inject the service dependency into the controller in ASP.NET Core?

How can we inject the service dependency into the controller C# Asp.net Core? ASP.NET Core injects objects of dependency classes through constructor or method by using built-in IoC container. The built-in container is represented by IServiceProvider implementation that supports constructor injection by default.


1 Answers

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.

like image 148
AgentFire Avatar answered Sep 19 '22 22:09

AgentFire