8

My .net core app needs to crawl data in a specified time interval. I have chosen to implement IHostedService to run it in parallel with API. The hosted service needs some services injected. I register them in startup.cs, but I get an error:

System.InvalidOperationException: 'Cannot consume scoped service 'IXService' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'

My startup.cs:

services.AddScoped<IXService, XService>();
services.AddHostedService<MyHostedService>();

I had a similar problem yet with DbContext, I solved it with https://stackoverflow.com/a/48368934/8475133, but this time I need dependency injection going through deeper layers and dealing with IServiceScopeFactory in each doesn't seem to be an elegant solution.

Michal Rosenbaum
  • 1,801
  • 1
  • 10
  • 18
  • 1
    I don't get it. If you could resolve the same problem for `DbContext` why couldn't you resolve it for the rest of the services `MyHostedService` needs? – Camilo Terevinto Aug 25 '18 at 20:17
  • "...doesn't seem to be elegant solution", Suppose i have many services to inject deep down, each consume in constructor some other services, this solution results in additional lines of code with usings, while standard dependency injection is just couple of lines with registering services and then including them in parameters of constructor it is much more clear. – Michal Rosenbaum Aug 25 '18 at 20:43
  • Yet you accepted the answer that is a duplicate from the link you posted on the question? – Camilo Terevinto Aug 25 '18 at 20:44

1 Answers1

15

The reason you're not allowed to do this is because MyHostedService has a singleton lifetime, which is a longer lifetime than scoped. The basic assumption here is that a service that is registered as scoped should not be kept alive indefinitely, this could easily happen if a singleton service keeps a reference to a scoped service.

I think the solution you're looking for is to inject IServiceProvider into MyHostedService, use it to create a new scope and new XService instance whenever you need it.

That is, replace

_xService.Foo();

with

using(var scope = _serviceProvider.CreateScope()) {
    var xService = scope.ServiceProvider.GetService<IXService>();
    xService.Foo();
}

An alternative, of course, would be to simply register XService as a singleton, by just replacing the call to AddScoped with AddSingleton, but I would not recommend it.

Edit: I must admit to not reading your link before posting my response. However, I still think this is the most elegant solution.

Håkon Kaurel
  • 221
  • 2
  • 6
  • 2
    This not completely correct. AddHostedService is adding them as transient but they are only created once by the HostedServiceExecutor https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.internal.hostedserviceexecutor?view=aspnetcore-2.1. – alsami Aug 26 '18 at 06:37
  • Thank you for the correction, I misread the error message. It clearly says HostedServiceExecutor. – Håkon Kaurel Aug 26 '18 at 10:53
  • 2
    Just to update. In Nov of 2018, the code was changed to do a register as singleton which is very important. It allows other requests to retrieve the same hosted service as a singleton. See AspNetCore/#4147 make AddHostedService register singletons on github https://github.com/dotnet/extensions/commit/75c680d686504552e5ddde047400a165f2f70be2 – SOHO Developer Feb 24 '21 at 20:29