Cannot consume scoped service from singleton
So this is an error message logged in the event viewer from a dotnet core app that I am building as a PoC and the problem is that although I understand the error message, I don't understand why it is occurring, although I have managed to get rid of it!
Hopefully you understand that when you register services in an IoC container, you set a lifetime that dictates how long the service is used for before it is disposed. Although you want to create as few as possible, having everything as a singleton would mean making everything completely thread safe and also potentially creating a bottleneck in the system. On the other hand, making everything transient would mean that you are probably creating far too many objects and also you lose the ability to set the property of an object and know that other objects in the same scope can see the change. Scoped is a good balance where you can define a lifetime (like the lifetime of an HTTP request) which is usually the best balance.
One of the potential problems is that you cannot inject an object with a shorter lifetime than the current object into its constructor (other than transients which are sometimes allowed). This is to avoid a singleton keeping a lock on a scoped object which is probably not what you intended. In dev mode, dotnet throws a dirty big exception and gives you a reasonable useful error message which might make sense but didn't for me:
Application startup exception: System.InvalidOperationException: Cannot consume scoped service 'System.Collections.Generic.IEnumerable`1[Microsoft.Extensions.Options.IConfigureOptions`1[Microsoft.AspNetCore.Authentication.AuthenticationOptions]]' from singleton 'Microsoft.Extensions.Options.IOptions`1[Microsoft.AspNetCore.Authentication.AuthenticationOptions]'.
The reason this was confusing is:
The solution in my case was to be more specific in my registrations. I was using Scrutor to get everything from an assembly of repositories (which works in the other app) but there is one class that cannot be directly created by IoC and which has an extension method to do so. Perhaps because this was registered in the other app and not in this one meant at runtime, mine tried to find some strings for the Constructor and fell over strangely! By being more specific and only scanning things that are IRepository, it started working again.
Hopefully you understand that when you register services in an IoC container, you set a lifetime that dictates how long the service is used for before it is disposed. Although you want to create as few as possible, having everything as a singleton would mean making everything completely thread safe and also potentially creating a bottleneck in the system. On the other hand, making everything transient would mean that you are probably creating far too many objects and also you lose the ability to set the property of an object and know that other objects in the same scope can see the change. Scoped is a good balance where you can define a lifetime (like the lifetime of an HTTP request) which is usually the best balance.
One of the potential problems is that you cannot inject an object with a shorter lifetime than the current object into its constructor (other than transients which are sometimes allowed). This is to avoid a singleton keeping a lock on a scoped object which is probably not what you intended. In dev mode, dotnet throws a dirty big exception and gives you a reasonable useful error message which might make sense but didn't for me:
Application startup exception: System.InvalidOperationException: Cannot consume scoped service 'System.Collections.Generic.IEnumerable`1[Microsoft.Extensions.Options.IConfigureOptions`1[Microsoft.AspNetCore.Authentication.AuthenticationOptions]]' from singleton 'Microsoft.Extensions.Options.IOptions`1[Microsoft.AspNetCore.Authentication.AuthenticationOptions]'.
The reason this was confusing is:
- It only happened when I introduced some IoC registrations for types in some of our shared libraries
- These libraries worked fine in another (not dotnet core) application
- These libraries and my app do not reference AuthenticationOptions anywhere
- If I changed the lifetime of the imported types, it broke my MVC routing!
The solution in my case was to be more specific in my registrations. I was using Scrutor to get everything from an assembly of repositories (which works in the other app) but there is one class that cannot be directly created by IoC and which has an extension method to do so. Perhaps because this was registered in the other app and not in this one meant at runtime, mine tried to find some strings for the Constructor and fell over strangely! By being more specific and only scanning things that are IRepository, it started working again.