1

I have not had much experience using Dependency Injection but I am trying to use it in one of my projects. When I try to add a second parameter to the constructor of my I get an error "Cannot consume scoped service 'CarbonService' from singleton..." Not sure why I get this error or what I am missing. Thanks for your help

enter image description here

I would like my timer class to have access to the CarbonService object.

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  services.AddDbContext<CarbonDBContext>(o => o.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
  services.AddHostedService<ValidUntilTimerService>();
  services.AddScoped<ICarbonService, CarbonService>();  // I HAVE TRIED USING SINGLETON BUT SAME ERROR 

  services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true));
}

ValidUntilTimerService.cs:

// ADDING THE SECOND PARAMETER TO THIS CONSTRUCTOR CAUSES THE ERROR.
public class ValidUntilTimerService : IHostedService, IDisposable
{
  private readonly ILogger _logger;
  private Timer _timer;
  private ICarbonService _carbonService;

  public ValidUntilTimerService(ILogger<ValidUntilTimerService> logger, ICarbonService carbonService)
  {
    _logger = logger;
    _carbonService = carbonService; 
  }
  ...
}

CarbonService.cs:

public interface ICarbonService
{
  WattTime ReadCarbon();
  void SaveCarbon(int carbonIndex);
}

public class CarbonService : ICarbonService
{
  private IConfiguration _configuration;
  private readonly CarbonDBContext _dbcontext;

  public CarbonService(IConfiguration configuration, CarbonDBContext dbContext)
  {
    _configuration = configuration;
    _dbcontext = dbContext;
  }

  public WattTime ReadCarbon()
  {
    var wt = new WattTime();
    ...
    return wt;
  }

  public void SaveCarbon(int carbonIndex)
  {
    ...
  }
}
Ashkan Mobayen Khiabani
  • 33,575
  • 33
  • 102
  • 171
PKonstant
  • 834
  • 2
  • 15
  • 32
  • 1
    https://learn.microsoft.com/de-de/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2#consuming-a-scoped-service-in-a-background-task – Jota.Toledo Mar 26 '19 at 22:12
  • 2
    Possible duplicate of [Cannot consume scoped service IMongoDbContext from singleton IActiveUsersService after upgrade to ASP.NET Core 2.0](https://stackoverflow.com/questions/45810851/cannot-consume-scoped-service-imongodbcontext-from-singleton-iactiveusersservice) – devNull Mar 26 '19 at 22:15
  • 1
    [Also See: Captive Dependency](https://blog.ploeh.dk/2014/06/02/captive-dependency/) – JSteward Mar 26 '19 at 22:17
  • What do you think that error means? – mjwills Mar 26 '19 at 23:04

1 Answers1

1

IHostedServices are registered as singletons. You are injecting ICarbonService into ValidUntilTimerService, which means you are injecting a scoped service into a singleton service.

If we consider the lifetimes of these two types of services:

  • Scoped services are created once per request. This means, when a scoped service is instantiated, it stays alive until the end of the request.

  • When singleton service gets instantiated, it stays alive until the app shuts down.

we realize that it doesn't make much sense to consume scoped service from a singleton service. This is what would happen in that situation:

When singleton service (ValidUntilTimerService in your case) gets instantiated, its scoped dependency (ICarbonService in your case) also gets instantiated and injected to the singleton service. When the request is completed, the singleton service stays alive, but the scoped service gets disposed. Now your singleton service ended up with a "dead" dependency (_carbonService field in your case).

The above scenario is not possible. It is just a "what if" scenario. The point is, you cannot consume a service that is created per request from a service that can be created without request (e.g. upon app startup).

Now, that explains the cause of your issue, but let's see what you can do to solve it.

POSSIBLE SOLUTION

You can use IServiceScopeFactory to create your own scope inside of the ValidUntilTimerService:

public class ValidUntilTimerService : IHostedService, IDisposable
{
  private readonly ILogger _logger;
  private Timer _timer;
  private IServiceScopeFactory _serviceScopeFactory;

  public ValidUntilTimerService(ILogger<ValidUntilTimerService> logger, IServiceScopeFactory serviceScopeFactory)
  {
    _logger = logger;
    _serviceScopeFactory = serviceScopeFactory; 
  }

  // ...
  using (var scope = _serviceScopeFactory.CreateScope())
  {
     ICarbonService carbonService = scope.ServiceProvider.GetService(typeof(ICarbonService));
    // ...
  }
  // ...
}
Marko Papic
  • 1,846
  • 10
  • 23
  • Thank you for your explanation. It made sense. I made your suggestions and things worked. Question, why wouldn't changing "services.AddSingleton();" Fyi, it doesn't. I tried it – PKonstant Mar 27 '19 at 14:13