Cannot Consume Scoped Service From Singleton – A Lesson In ASP.net Core DI Scopes

While helping a new developer get started with ASP.net Core, they ran into an interesting exception :

InvalidOperationException: Cannot consume scoped service from singleton.

I found it interesting because it’s actually the Service DI of ASP.net Core trying to make sure you don’t trip yourself up. Although it’s not foolproof (They still give you enough rope to hang yourself), it’s actually trying to stop you making a classic DI scope mistake. I thought I would try and build up an example with code to first show them, then show you here! Of course, if you don’t care about any of that, there is a nice TL;DR right at the end of this post!

The Setup

So there is a little bit of code setup before we start explaining everything. The first thing we need is a “Child” service :

The reason we have a property here called “CreationCount” is because later on we are going to test if a service is being created on a page refresh, or it’s re-using existing instances (Don’t worry, it’ll make sense!)

Next we are going to create *two* parent classes. I went with a Mother and Father class, for no other reason that the names seemed to fit (They are both parent classes). They also will have a creation count, and reference the child service.

Finally, we should create a controller that simply outputs how many times the services have been created. This will help us later in understanding how our service collection is created and, in some circumstances, shared.

The Scoped Service Problem

The first thing we want to do, is add a few lines to the ConfigureServices method of our startup.cs. This first time around, all services will be singletons.

Now, let’s load our page and refresh if a few times and see what the output is. The results of 3 page refreshes look like so :

So this makes sense. A singleton is one instance per application, so no matter how many times we fresh the page, it’s never going to create a new instance.

Let’s change things up, let’s make everything transient.

And then we load it up 3 times.

It’s what we would expect. The “parent” classes go up by 1 each time. The child actually goes up by 2 because it’s being “requested” twice per page load. One for the “mother” and one for the “father”.

Let’s instead make everything scoped. Scoped means that a new instance will be created essentially per page load (Atleast that’s the “scope” within ASP.net Core).

And now let’s refresh out page 3 times.

OK this makes sense. Each page load, it creates a new instance for each. But unlike the transient example, our child creation only gets created once per page load even though two different services require it as a dependency.

Right, so we have everything as scoped. Let’s try something. Let’s say that we decide that our parents layer should be singleton. There could be a few reasons we decide to do this. There could be some sort of “cache” that we want to use. It might have to hold state etc. The one reason we don’t want to make it singleton is for performance. Very very rarely do I actually see any benefit from changing services to singletons. And the eventual screw ups because you are making things un-intuitively singletons is probably worse in the long run.

We change our ConfigureServices method to :

We hit run and….

So because our ChildService is scoped, but the FatherService is singleton, it’s not going to allow us to run. So why?

If we think back to how our creation counts worked. When we have a scoped instance, each time we load the page, a new instance of our ChildService is created and inserted in the parent service. Whereas when we do a singleton, it keeps the exact same instance (Including the same child services). When we make the parent service a singleton, that means that the child service is unable to be created per page load. ASP.net Core is essentially stopping us from falling in this trap of thinking that a child service would be created per page request, when in reality if the parent is a singleton it’s unable to be done. This is why the exception is thrown.

What’s the fix? Well either the parent needs to be scoped or transient scope. Or the child needs to be a singleton itself. The other option is to use a service locator pattern but this is not really recommended. Typically when you make a class/interface singleton, it’s done for a very good reason. If you don’t have a reason, make it transient.

The Singleton Transient Trap

The interesting thing about ASP.net Core catching you from making a mistake when a scoped instance is within a singleton, is that the same “problem” will arise from a transient within a singleton too.

So to refresh your memory. When we have all our services as transient :

We load the page 3 times, and we see that we create a new instance every single time the class is requested.

So let’s try something else. Let’s make the parent classes singleton, but leave the child as a transient.

So we load the page 3 times. And no exception but…

This is pretty close as having the child as scoped with the parents singletons. It’s still going to give us some unintended behaviour because the transient child is not going to be created “everytime” as we might first think. Sure, it will be created everytime it’s “requested”, but that will only be twice (Once for each of the parents).

I think the logic behind this not throwing an exception, but scoped will, is that transient is “everytime this service is requested, create a new instance”, so technically this is correct behaviour (Even though it’s likely to cause issues). Whereas a “scoped” instance in ASP.net Core is “a new instance per page request” which cannot be fulfilled when the parent is singleton.

Still, you should avoid this situation as much as possible. It’s highly unlikely that having a parent as a singleton and the child as transient is the behaviour you are looking for.

TL;DR;

A Singleton cannot reference a Scoped instance.

 

ENJOY THIS POST?
Join over 3.000 subscribers who are receiving our weekly post digest, a roundup of this weeks blog posts.
We hate spam. Your email address will not be sold or shared with anyone else.

5 comments

    1. I ran into this because Assembly Microsoft.EntityFrameworkCore, Version=2.0.1.0 AddDbContext registers the application’s context as a scoped service.

  1. One scenario where a singleton may need a transient is when the child is a database repository. The singleton may need this to initialize a collection, but after that, the transient repository will never be used…so things work out. Just be sure to init your parent singleton in ConfigureServices by calling serviceProvider.GetService.

    1. @paultechguy can you perhaps elaborate on this? I need a singleton service that will keep all my configuration settings from the database but I get the same issue since my Context is DI into the Service I want make singleton.

      1. You would need to inject them both independently. Which would make sense as a Context is a living object, it’s not a setting.

Leave a Reply

Your email address will not be published. Required fields are marked *