Injecting a Scoped Service into IHostedService in .NET 6/7

In .NET 6 and 7, background services (IHostedService) are often used for tasks like long-running processes. These services run in the background and might need to access scoped services (e.g., DbContext). Directly injecting a scoped service into IHostedService causes issues because they have different lifetimes.

To solve this, we can use IServiceScopeFactory to create a scope for each background task. This allows us to resolve the scoped service properly within the background service, ensuring it’s disposed correctly.

Here’s an updated example of how to implement this in a modern .NET application:

public class MyBackgroundService : IHostedService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<MyBackgroundService> _logger;

    public MyBackgroundService(IServiceScopeFactory scopeFactory, ILogger<MyBackgroundService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Create a scope for each background task
        var scope = _scopeFactory.CreateScope();
        var myScopedService = scope.ServiceProvider.GetRequiredService<MyScopedService>();

        // You can now use myScopedService as needed
        _logger.LogInformation("Background service started.");
        
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Background service stopping.");
        return Task.CompletedTask;
    }
}

Key Changes for .NET 6/7:

  • Minimal Hosting Model: The application startup code has changed in .NET 6 and 7, favoring the use of a simplified Program.cs.
  • Service Registration: Make sure MyScopedService is registered with a scoped lifetime in ConfigureServices:
builder.Services.AddScoped<MyScopedService>();
builder.Services.AddHostedService<MyBackgroundService>();

Best Practices:

  1. Error Handling: Implement robust error handling within your background services to prevent failures.
  2. Logging: Use ILogger<T> for effective logging, especially in background services that may run without direct user interaction.
  3. Cancellation Token: Always respect the CancellationToken passed to the StartAsync and StopAsync methods to gracefully handle shutdowns.

Performance Considerations:

Creating a scope for each background task may introduce some overhead. If possible, try to balance between scoped services and singleton services, depending on your use case.

For more advanced setups, consider using IServiceProvider directly or other patterns like IBackgroundTaskQueue for better task management.

Unknown's avatar

Author: Peter Groenewegen

Hi, I’m Peter Groenewegen—a technologist, developer advocate, and AI enthusiast passionate about building tools that truly fit people’s workflows. My journey in tech has been one of innovation, collaboration, and a relentless curiosity to make the complex simple.

8 thoughts on “Injecting a Scoped Service into IHostedService in .NET 6/7”

  1. Hi – I’m battling with IHostedService at this very moment!

    Would you use IHostedService to execute a long running task from an action method, or should IHostedService only be used for periodic tasks that run in the background? If you can execute from an action method, how would you pass additional parameters to ExecuteAsync?

    Liked by 1 person

    1. Hi Robin, basically would use the ExecuteAsync method to ‘administer’ your long running process. When using the BackgroundService class from the framework you are not able to change the parameters. In the blog post https://pgroene.wordpress.com/2018/05/31/run-scheduled-background-tasks-in-asp-net-core you can find some samples to implement the IHostedService interface yourself. If possible I would try to cut the ‘long’ running process into smaller parts to have more control over what is happening and for example handle when the application shuts down.

      Like

  2. Hi Peter, I’m facing with the same issue. Can you tell me is this independent class or this should be a part of startup class?
    For scheduled task, I re-used your solution that you provided on GitHub.
    Thanks in advance!

    Like

    1. When you start a new task from a scheduler, it does not have a scope. If you want to use classes that run in a scope like a dbconnection you should add the:
      using (var scope = _serviceScopeFactory.CreateScope())
      {
      IScoped scoped = scope.ServiceProvider.GetRequiredService();

      //Do your stuff
      }
      In the Do Your Stuff part you can use the scope.ServiceProvider.GetRequiredService to create the top level obect in you your scope that will do the processing.

      Like

      1. Hi, Peter.
        Thank you very much for your time and answer!
        Please take a look of this image: https://ibb.co/YjTLwPg
        I added this part of the code in ScheduleTask.cs but, I’m getting the error with a message: ‘The type arguments for method ‘ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider)’ cannot be inferred from the usage. Try specifying the type arguments explicitly. ‘.

        I tried to find solution, but with no succes.

        Thanks in advance!

        Like

  3. Hello Peter,

    Like other readers… I cannot thank you enough for this short but very crucial post! I was struggling to use Asp.Net Identity from insider a Worker Service project because of this exact situation – The Worker is registered as a Singleton while the UserManager of Identity services is registered as Scoped and your post helped me bridge the gap.

    Again thank you very much for this public service to the developer community!!

    Kind Regards…

    PS: Hope you keep up the good work so minions like me can learn from folks like you!

    Like

Leave a reply to Tech Enthusiast Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.