access BackgroundService from controller in asp.net core 2.1
Asked Answered
C

3

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?

Choreography answered 28/3, 2018 at 13:6 Comment(3)
add constructor to controller public ControllerName(IHostedService service){ .. } Did you try to read documentation before asking here? learn.microsoft.com/en-us/aspnet/core/fundamentals/…Pyromancy
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<IHostedService,...>()Choreography
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 itPublishing
P
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));
  }
}
Promulgate answered 27/10, 2018 at 22:5 Comment(3)
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.Siret
@Siret 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.Muldon
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.Corcovado
C
10

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

Choreography answered 28/3, 2018 at 17:48 Comment(0)
P
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));
  }
}
Promulgate answered 27/10, 2018 at 22:5 Comment(3)
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.Siret
@Siret 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.Muldon
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.Corcovado
F
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).

Fawnia answered 30/6, 2019 at 10:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.