Use scoped services within a BackgroundService
When you register implementations of IHostedService using any of the AddHostedService extension methods - the service is registered as a singleton. There may be scenarios where you'd like to rely on a scoped service. For more information, see Dependency injection in .NET: Service lifetimes.
In this tutorial, you learn how to:
- Resolve scoped dependencies in a singleton BackgroundService.
- Delegate work to a scoped service.
- Implement an
override
of BackgroundService.StopAsync(CancellationToken).
Tip
All of the "Workers in .NET" example source code is available in the Samples Browser for download. For more information, see Browse code samples: Workers in .NET.
Prerequisites
- The .NET 8.0 SDK or later
- A .NET integrated development environment (IDE)
- Feel free to use Visual Studio
Create a new project
To create a new Worker Service project with Visual Studio, you'd select File > New > Project.... From the Create a new project dialog search for "Worker Service", and select Worker Service template. If you'd rather use the .NET CLI, open your favorite terminal in a working directory. Run the dotnet new
command, and replace the <Project.Name>
with your desired project name.
dotnet new worker --name <Project.Name>
For more information on the .NET CLI new worker service project command, see dotnet new worker.
Tip
If you're using Visual Studio Code, you can run .NET CLI commands from the integrated terminal. For more information, see Visual Studio Code: Integrated Terminal.
Create scoped services
To use scoped services within a BackgroundService
, create a scope. No scope is created for a hosted service by default. The scoped background service contains the background task's logic.
namespace App.ScopedService;
public interface IScopedProcessingService
{
Task DoWorkAsync(CancellationToken stoppingToken);
}
The preceding interface defines a single DoWorkAsync
method. To define the default implementation:
- The service is asynchronous. The
DoWorkAsync
method returns aTask
. For demonstration purposes, a delay of ten seconds is awaited in theDoWorkAsync
method. - An ILogger is injected into the service.:
namespace App.ScopedService;
public sealed class DefaultScopedProcessingService(
ILogger<DefaultScopedProcessingService> logger) : IScopedProcessingService
{
private int _executionCount;
public async Task DoWorkAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
++ _executionCount;
logger.LogInformation(
"{ServiceName} working, execution count: {Count}",
nameof(DefaultScopedProcessingService),
_executionCount);
await Task.Delay(10_000, stoppingToken);
}
}
}
The hosted service creates a scope to resolve the scoped background service to call its DoWorkAsync
method. DoWorkAsync
returns a Task
, which is awaited in ExecuteAsync
:
Rewrite the Worker class
Replace the existing Worker
class with the following C# code, and rename the file to ScopedBackgroundService.cs:
namespace App.ScopedService;
public sealed class ScopedBackgroundService(
IServiceScopeFactory serviceScopeFactory,
ILogger<ScopedBackgroundService> logger) : BackgroundService
{
private const string ClassName = nameof(ScopedBackgroundService);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
"{Name} is running.", ClassName);
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
"{Name} is working.", ClassName);
using (IServiceScope scope = serviceScopeFactory.CreateScope())
{
IScopedProcessingService scopedProcessingService =
scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWorkAsync(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
"{Name} is stopping.", ClassName);
await base.StopAsync(stoppingToken);
}
}
In the preceding code, an explicit scope is created and the IScopedProcessingService
implementation is resolved from the dependency injection service scope factory. The resolved service instance is scoped, and its DoWorkAsync
method is awaited.
Replace the template Program.cs file contents with the following C# code:
using App.ScopedService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ScopedBackgroundService>();
builder.Services.AddScoped<IScopedProcessingService, DefaultScopedProcessingService>();
IHost host = builder.Build();
host.Run();
The services are registered in (Program.cs). The hosted service is registered with the AddHostedService
extension method.
For more information on registering services, see Dependency injection in .NET.
Verify service functionality
To run the application from Visual Studio, select F5 or select the Debug > Start Debugging menu option. If you're using the .NET CLI, run the dotnet run
command from the working directory:
dotnet run
For more information on the .NET CLI run command, see dotnet run.
Let the application run for a bit to generate several execution count increments. You will see output similar to the following:
info: App.ScopedService.ScopedBackgroundService[0]
ScopedBackgroundService is running.
info: App.ScopedService.ScopedBackgroundService[0]
ScopedBackgroundService is working.
info: App.ScopedService.DefaultScopedProcessingService[0]
DefaultScopedProcessingService working, execution count: 1
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\scoped-service
info: App.ScopedService.DefaultScopedProcessingService[0]
DefaultScopedProcessingService working, execution count: 2
info: App.ScopedService.DefaultScopedProcessingService[0]
DefaultScopedProcessingService working, execution count: 3
info: App.ScopedService.DefaultScopedProcessingService[0]
DefaultScopedProcessingService working, execution count: 4
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.ScopedService.ScopedBackgroundService[0]
ScopedBackgroundService is stopping.
If running the application from within Visual Studio, select Debug > Stop Debugging.... Alternatively, select Ctrl + C from the console window to signal cancellation.