Search code examples
c#asp.net-core.net-corewindows-servicesbackgroundworker

Dependency Injection in ASP.NET Core Worker Service


I'm facing some dependency injection issues in .NET Core Worker Service. Please see the below code in Program.cs file.

public static void Main(string[] args)
{
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .MinimumLevel.Override("Microsof)t", LogEventLevel.Warning)
        .Enrich.FromLogContext()
        .WriteTo.File(@"C:\MyApp_Log\Log.txt")
        .CreateLogger();

    try
    {
        Log.Information("Starting up the service.");
        CreateHostBuilder(args).Build().Run();
        return;
    }
    catch (Exception ex)
    {
        Log.Fatal(ex, "There was a problem starting the service");
        return;
    }
    finally
    {
        Log.CloseAndFlush();
    }

}
public static IHostBuilder CreateHostBuilder(string[] args)
{

    return Host.CreateDefaultBuilder(args)
        .UseWindowsService()
        .ConfigureServices((hostContext, services) =>
        {

            services.AddScoped<IMyAppCoreService, MyAppCoreService>();

            services.AddDbContext<MyAppCSContext>(options => options.UseSqlServer("Data Source=xx.xxx.xx.xxx;Database=Mydb;User ID = sa;Password=mypassword"));


            services.AddHostedService<Worker>();
        })
        .UseSerilog();
}

And please see below code for Worker.cs file

private readonly ILogger<Worker> _logger;
private readonly IMyAppCoreService _CoreService;

public Worker(ILogger<Worker> logger, IMyAppCoreService CoreService)
{
    _logger = logger;
    _CoreService = CoreService;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
    _logger.LogInformation("The MyApp_CoreService has been Started...");
    return base.StartAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
    _logger.LogInformation("The MyApp_CoreService has been stopped...");
    return base.StopAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        _CoreService.CheckAndProcessResult();
        await Task.Delay(1000, stoppingToken);
    }
}

When I run the above query, I got the below query.

Error while validating the service descriptor

ServiceType: MyApp.CS.Business.Facade.IMyAppCoreService Lifetime: Scoped ImplementationType: MyApp.CS.Business.Services.MyAppCoreService': Unable to resolve service for type 'MyApp.CS.Data.Facade.ICommonRepository' while attempting to activate 'MyApp.CS.Business.Services.MyAppCoreService'.

Can you please tell me where I was done wrong?

EDIT: After i register all the interface with its class. then i got new error as follows.

Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: MyApp_CoreService.Worker': Cannot consume scoped service 'MyApp.CS.Business.Facade.IMyAppCoreService' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.


Solution

  • You injected the Serivce IMyAppCoreService as Scoped. Scoped Services can only be resolved by a ScopedServiceProvider.

    My first guess is that you didn't mean to - you meant to inject your service as Singleton:

    services.AddSingleton<IMyAppCoreService, MyAppCoreService>();
    

    This however might not work since you are using EF Core which injects its Context-like classes as scoped. You have two options:

    1. Have EF Core inject its context class as transient. I would not suggest this as ~you will be responsible for disposing it~ you might get surprised by receiving different instances. If you want to go through with it, this is how to do it (Startup.cs / Program.cs):
    services.AddDbContext<YourContext>(opts => { ...config...}, ServiceLifetime.Transient);
    
    1. Create a scope manually:

    Have an IServiceProvider property called ServiceProvider injected into Worker instead of your service.

    in ExecuteAsync-Loop:

    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
    using (var scope = ServiceProvider.CreateScope())
    {
      scope.ServiceProvider.GetRequiredService<IMyAppCoreService>().CheckAndProcessResult();
    }
    await Task.Delay(1000, stoppingToken);
    

    This will neatly dispose every Loop's EFCore-Data Context Object and in my opinion is the cleanest option.

    Edit (2024): I have been made aware that this contains incorrect information. On https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines it is documented that you should not dispose Services resolved from DI in dotnet.