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:

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

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 a Task. For demonstration purposes, a delay of ten seconds is awaited in the DoWorkAsync 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.

See also