11

I just need to access my BackgroundService from a controller. Since BackgroundServices are injected with

services.AddSingleton<IHostedService, MyBackgroundService>()

How can I use it from a Controller class?

ekad
  • 14,436
  • 26
  • 44
  • 46
Alex Troto
  • 651
  • 6
  • 18
  • 1
    add constructor to controller `public ControllerName(IHostedService service){ .. }` Did you try to read documentation before asking here? https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection – Roman Marusyk Mar 28 '18 at 13:08
  • 3
    Yes, I did. I need BackgroundService injected in my controller, not IHostedService interface. I can have more than one BackgroundService, and all are injected as services.AddSingleton() – Alex Troto Mar 28 '18 at 13:32
  • 2
    Something to be very careful about is aspnet registers HostedServices as **transient** instances. So any time you receive an instance, it is **not** the instance that had `Start()` invoked on it – Chris Marisic Nov 12 '18 at 22:58

3 Answers3

9

In the end I've injected IEnumerable<IHostedService> in the controller and filtered by Type:background.FirstOrDefault(w => w.GetType() == typeof(MyBackgroundService)

Alex Troto
  • 651
  • 6
  • 18
4

This is how I solved it:

public interface IHostedServiceAccessor<T> where T : IHostedService
{
  T Service { get; }
}

public class HostedServiceAccessor<T> : IHostedServiceAccessor<T>
  where T : IHostedService
{
  public HostedServiceAccessor(IEnumerable<IHostedService> hostedServices)
  {
    foreach (var service in hostedServices) {
      if (service is T match) {
        Service = match;
        break;
      }
    }
  }

  public T Service { get; }
}

Then in Startup:

services.AddTransient<IHostedServiceAccessor<MyBackgroundService>, HostedServiceAccessor<MyBackgroundService>>();

And in my class that needs access to the background service...

public class MyClass
{
  private readonly MyBackgroundService _service;

  public MyClass(IHostedServiceAccessor<MyBackgroundService> accessor)
  {
    _service = accessor.Service ?? throw new ArgumentNullException(nameof(accessor));
  }
}
Doug Wilson
  • 4,185
  • 3
  • 30
  • 35
  • A word of warning--after encountering some unusual behavior, note that while the IHostedService implementation acts like a singleton, its state will be inaccessible. For example, if you add `private readonly Guid _id = Guid.NewGuid();` to the service class and observe it when the service starts vs when obtained via IHostedServiceAccessor, you'll notice it's two different values. – UtopiaLtd Apr 30 '19 at 21:40
  • @UtopiaLtd Don't add the background service as `Transient` but as a `Singleton`. The microsoft documentation states that the hosted service must create the scopes by itself, as if you add it as `Scoped`, you'll probably use disposed objects. – Emy Blacksmith Nov 03 '20 at 12:31
  • As of .NET 5.0 an API derives from ControllerBase. It would be better if this answer addressed how a "MyController" deriving from ControllerBase is to invoke BackgroundService. – H2ONaCl Nov 30 '20 at 06:45
0

Add BackgroundService in ConfigureServices function:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHostedService<ListenerService>();


        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

Inject in the Controller:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHostedService listenerService;

    public ValuesController(IHostedService listenerService)
    {
        this.listenerService = listenerService;
    }
}

I used BackgroundService to spin up multiple listeners for AWSSQS Listeners. If consumer wants to spin new listener then it could be done by POSTing to a Controller method (end point).

Sukhminder Sandhu
  • 696
  • 1
  • 5
  • 6