Let’s say you have a hosted BackgroundService called DatabaseLoggerService. It runs in the background and logs messages to the database. It has the following definition:
public class DatabaseLoggerService : BackgroundService, ILoggerService
Code language: C# (cs)
You want your controllers to use this for logging. In other words, you want them to depend on ILoggerService, not the concrete DatabaseLoggerService class.
First, constructor inject ILoggerService into your controllers, like this:
[Route("[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
private readonly ILoggerService Logger;
public RecipesController(ILoggerService logger)
{
Logger = logger;
}
//rest of controller
}
Code language: C# (cs)
Then register DatabaseLoggerService as both an ILoggerService and as a HostedService:
//rest of adding services
var loggerService = new DatabaseLoggerService();
builder.Services.AddSingleton<ILoggerService>(loggerService);
builder.Services.AddHostedService(_ => loggerService);
//rest of init code
Code language: C# (cs)
Note: Before .NET 6, do this in Startup.ConfigureServices().
When a request comes in, the framework will pass the DatabaseLoggerService instance to the controller. This is because it’s registered as ILoggerService. The controller can use the logging functionality. It doesn’t need to know about the concrete type or the fact that it’s a BackgroundService.
Resolving BackgroundService dependencies
If your BackgroundService has dependencies:
- Create the instance in AddSingleton() and use GetService() to resolve its dependencies.
- In AddHostedService(), call GetService() to get your instance and cast it to the concrete type.
Here’s an example. Let’s say DatabaseLoggerService (example from above) is dependent on IHostApplicationLifetime. Here’s how to register it and its dependencies:
//rest of adding services
builder.Services.AddSingleton<ILoggerService>(sp =>
{
var hostAppLifetime = sp.GetService<IHostApplicationLifetime>();
return new DatabaseLoggerService(hostAppLifetime);
});
builder.Services.AddHostedService(sp => sp.GetService<ILoggerService>() as DatabaseLoggerService);
//rest of init code
Code language: C# (cs)
Note: Before .NET 6, do this in Startup.ConfigureServices().
This is a bit unsafe, but we know for sure that the ILoggerService we registered is definitely a DatabaseLoggerService, so it’s fine.
Why not inject IHostedService or the concrete class?
You’ve probably heard the saying: “Code against interfaces, not implementations.“
Does the controller need to know that it’s using a BackgroundService? In most cases, no. In the examples above, the controllers only need to know they’re using an ILoggerService. They don’t need to know about the concrete DatabaseLoggerService class.
Furthermore, it wouldn’t be a good idea to give the controllers access to the BackgroundService methods, such as StopAsync(). This would be undesirable in most cases. Here’s an example:
[Route("[controller]")]
[ApiController]
public class RecipesController : ControllerBase
{
private readonly DatabaseLoggerService Logger;
public RecipesController(DatabaseLoggerService logger)
{
Logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
await Logger.StopAsync(HttpContext.RequestAborted);
//rest of method
}
}
Code language: C# (cs)
Furthermore, injecting interfaces makes the controller easier to unit test because you mock out the interface.