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 might 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 with the IServiceScopeFactory.CreateScope() API. 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. Create an implementation in a new class named DefaultScopedProcessingService.cs:

namespace App.ScopedService;

public sealed class DefaultScopedProcessingService(
    ILogger<DefaultScopedProcessingService> logger) : IScopedProcessingService
{
    private readonly string _instanceId = Guid.NewGuid().ToString();

    public Task DoWorkAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation(
            "{ServiceName} doing work, instance ID: {Id}",
            nameof(DefaultScopedProcessingService),
            _instanceId);

        return Task.CompletedTask;
    }
}
  • An ILogger is injected into the service using a primary constructor.
  • The DoWorkAsync method returns a Task and accepts the CancellationToken.
    • The method logs the instance identifier—the _instanceId is assigned whenever the class is instantiated.

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);

        while (!stoppingToken.IsCancellationRequested)
        {
            using IServiceScope scope = serviceScopeFactory.CreateScope();

            IScopedProcessingService scopedProcessingService =
                scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWorkAsync(stoppingToken);

            await Task.Delay(10_000, stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation(
            "{Name} is stopping.", ClassName);

        await base.StopAsync(stoppingToken);
    }
}

In the preceding code, while the stoppingToken isn't canceled, the IServiceScopeFactory is used to create a scope. From the IServiceScope, the IScopedProcessingService is resolved. The DoWorkAsync method is awaited, and the stoppingToken is passed to the method. Finally, the execution is delayed for 10 seconds and the loop continues. Each time the DoWorkAsync method is called, a new instance of the DefaultScopedProcessingService is created and the instance identifier is logged.

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 calls to DoWorkAsync, thus logging new instance identifiers. You see output similar to the following logs:

info: App.ScopedService.ScopedBackgroundService[0]
      ScopedBackgroundService is running.
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService doing work, instance ID: 8986a86f-b444-4139-b9ea-587daae4a6dd
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 doing work, instance ID: 07a4a760-8e5a-4c0a-9e73-fcb2f93157d3
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService doing work, instance ID: c847f432-acca-47ee-8720-1030859ce354
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