Dependency inject BackgroundService into controllers

By FoxLearn 2/4/2025 8:21:18 AM   8
To inject a BackgroundService into controllers in ASP.NET Core, you can't directly inject it through constructor injection like a normal service.

The reason is that BackgroundService is intended to run in the background as part of the application's hosted services, and it doesn't follow the typical request/response lifecycle of controllers.

For example, how to inject a DatabaseLoggerService as a background service while using ILoggerService for dependency injection in controllers.

We’ll make sure to abstract away the specific implementation of DatabaseLoggerService from the controller, and ensure that ILoggerService is used for logging purposes in the controllers.

First, define the DatabaseLoggerService as a BackgroundService that implements an ILoggerService interface.

public interface ILoggerService
{
    Task LogAsync(string message);
}

public class DatabaseLoggerService : BackgroundService, ILoggerService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
    private readonly ILogger<DatabaseLoggerService> _logger;

    public DatabaseLoggerService(IHostApplicationLifetime hostApplicationLifetime, ILogger<DatabaseLoggerService> logger)
    {
        _hostApplicationLifetime = hostApplicationLifetime;
        _logger = logger;
    }

    public async Task LogAsync(string message)
    {
        // Log message to the database (you can replace this with actual DB logging logic)
        _logger.LogInformation($"Logging to database: {message}");
        await Task.CompletedTask;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // This is where background work can be handled
            await Task.Delay(1000, stoppingToken); // Simulate background work
        }
    }
}

Next, define the controller that will use the ILoggerService for logging. The controller does not need to know about DatabaseLoggerService specifically.

[Route("[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
    private readonly ILoggerService _logger;

    public RecipesController(ILoggerService logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        await _logger.LogAsync($"Fetching recipe with ID: {id}");
        // Your logic here
        return Ok();
    }
}

In Program.cs (for .NET 6 and later), register the DatabaseLoggerService as both an ILoggerService and as a HostedService. This ensures that the service will run in the background and also be available to controllers via dependency injection.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                // Register DatabaseLoggerService as both ILoggerService and HostedService
                services.AddSingleton<ILoggerService>(sp =>
                {
                    var hostAppLifetime = sp.GetRequiredService<IHostApplicationLifetime>();
                    return new DatabaseLoggerService(hostAppLifetime, sp.GetRequiredService<ILogger<DatabaseLoggerService>>());
                });

                // Register as Hosted Service so it will run in the background
                services.AddHostedService<DatabaseLoggerService>();

                // Add controllers
                services.AddControllers();
            });
}

If DatabaseLoggerService has dependencies (like IHostApplicationLifetime), you can resolve them using GetRequiredService() or GetService() in the registration process. For example, if DatabaseLoggerService depends on IHostApplicationLifetime, you can register it as shown above. This is done by retrieving the required service (IHostApplicationLifetime) and passing it into the constructor of DatabaseLoggerService.

Why Abstract the Implementation?

In this setup, the controller doesn't directly interact with the DatabaseLoggerService implementation. Instead, it interacts with the ILoggerService interface, which allows you to change the logging mechanism later without changing the controller's code. The controller does not need to know about the background service or its lifecycle.

This follows the dependency inversion principle from SOLID design, where the controller depends on abstractions (interfaces) rather than concrete implementations.

Why Not Inject BackgroundService or the Concrete Class?

Controllers should not be concerned with the internal mechanics of a background service. By injecting the ILoggerService interface, you allow the service to be replaced with any other logging mechanism in the future (e.g., in-memory logging, file logging, etc.) without changing controller code.