Search code examples
c#dependency-injectionserviceasp.net-core-webapi.net-8.0

ASP.NET Core 8 Web API: cannot register scoped service from singleton


In my API I register some services with AddHostedService that need some interfaces to be injected on them, for example:

Program.cs:

builder.Services.AddScoped<IHypervisorRepository, HypervisorRepository>();
builder.Services.AddScoped<IAccountRepository, AccountRepository>();
builder.Services.AddScoped<ICloudStackDbUnitOfWork, CloudStackDbUnitOfWork>();
builder.Services.AddScoped<ICloudStackCoreConnectionFactory, MySqlCloudStackCoreConnectionFactory>();

builder.Services.AddScoped<IJobUtility, JobUtility>();
builder.Services.AddScoped<IVdcUnitOfWork, VdcUnitOfWork>();
builder.Services.AddScoped<IMemoryCache, MemoryCache>();

builder.Services.AddHostedService<CheckDatabaseHealthBackgroundService>();

CheckDatabaseHealthBackgroundService.cs:

public sealed class CheckDatabaseHealthBackgroundService : BackgroundService, IDisposable
{
    private readonly IServiceProvider _provider;
    private readonly IJobUtility _jobUtility;
    private readonly IVdcUnitOfWork _unitOfWork;
    private readonly ICloudStackCoreConnectionFactory _connFactory;
    private readonly IMemoryCache _cache;
    private readonly ILogger<CheckDatabaseHealthBackgroundService> _logger;
    
    public CheckDatabaseHealthBackgroundService(
        IServiceProvider provider,
        IJobUtility jobUtility,
        IVdcUnitOfWork unitOfWork,
        ICloudStackCoreConnectionFactory connFactory,
        IMemoryCache cache,
        ILogger<CheckDatabaseHealthBackgroundService> logger)
    {
        _provider = provider;
        _jobUtility = jobUtility;
        _unitOfWork = unitOfWork;
        _connFactory = connFactory;
        _cache = cache;
        _logger = logger;
    }

but after I launch the API the following Exception is thrown:

System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: CloudStackData.WebAPI.RefreshDiskTypeBackgroundService': Cannot consume scoped service 'Vdc.Libs.Data.IVdcUnitOfWork' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.

System.InvalidOperationException: Cannot consume scoped service 'Vdc.Libs.Data.IVdcUnitOfWork' from singleton 'Microsoft.Extensions.Hosting.IHostedService'

After reading about this error I concluded I should add the CheckDatabaseHealthBackgroundService required services as singleton, not scoped, and so I tried:

Program.cs:

builder.Services.AddScoped<IHypervisorRepository, HypervisorRepository>();
builder.Services.AddScoped<IAccountRepository, AccountRepository>();
builder.Services.AddScoped<ICloudStackDbUnitOfWork, CloudStackDbUnitOfWork>();
builder.Services.AddScoped<ICloudStackCoreConnectionFactory, MySqlCloudStackCoreConnectionFactory>();

builder.Services.AddSingleton<IJobUtility, JobUtility>();
builder.Services.AddSingleton<IVdcUnitOfWork, VdcUnitOfWork>();
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();

builder.Services.AddHostedService<CheckDatabaseHealthBackgroundService>();

And now the Exception thrown is:

System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: CloudStackData.WebAPI.RefreshDiskTypeBackgroundService': Cannot consume scoped service 'Vdc.Libs.Data.DbContextWrite' from singleton 'Vdc.Libs.Data.IVdcUnitOfWork'.

And here I started to be stuck.

VdcUnitOfWork is a custom class we created:

public sealed class VdcUnitOfWork : EFDbUnitOfWork<DbContextWrite>, IVdcUnitOfWork, IUnitOfWork, IDisposable, IDbContextWrapper
{
    public VdcUnitOfWork(DbContextWrite dbContext)
        : base(dbContext)
    {
    }
}

And DbContextWrite is another custom class:

public abstract class DbContextWrite : DbContext
{
    protected DbContextWrite([NotNull] DbContextOptions options)
        : base(options)
    {
    }
}

The fact is after adding the hosted service with AddHostedService I cannot run the API anymore (I have four hosted services, but just put one for the sake of simplification).


Solution

  • For using scoped-Services in a HostedService, which is a singleton, you need to inject the IServiceScopeFactory (using primary constructors from .NET 8.0):

    public sealed class CheckDatabaseHealthBackgroundService
          (IServiceScopeFactory scopeFactory) : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
             // get scoped services:
             using var scope = scopeFactory.CreateScope();
             var jobUtility = scope.ServiceProvider.GetService<IJobUtility>();
             var unitOfWork = scope.ServiceProvider.GetService<IVdcUnitOfWork>();
             var memoryCache = scope.ServiceProvider.GetService<IMemoryCache>();
    
             // Use services here
        }
    }