Sorry for the absolute word soup of a title on this post, but I wasn’t sure how else to describe this. Let me explain! In .NET Core/.NET 5+, you have this concept of “Environments”. Environments are used to describe your different environments or stages on the way to production. For example, local development, test, UAT, staging, production etc. You can have an unlimited number of environments, and there are no rules about what you call these environments (Well… Kinda as we shall soon see). Among other things, you can use environments to swap configurations, or do something only in production etc.
Recently, I was helping a developer debug an issue that he claimed was only happening on one environment (our CD environment in Azure), but could not be replicated in local development or in any environment beyond CD (e.x. Test did not have the same issue). I was a little skeptical but checked it out.
The error itself was the famed “Cannot Consume Scoped Service From Singleton“. It was actually a relatively easy fix in the end, but we were still puzzled why this issue only occurred on this one environment, and not others. It puzzled me for some time until I set up the code locally to try and replicate things, and realized that the naming scheme of various environments was… off.
Before we get to the solution, let’s take a step back!
“Known” .NET Environments
So earlier, and you can quote me on this, I said :
There are no rules about what you call these environments
Let’s walk that back a little. Theoretically you can call an environment anything, but there is actually only 3 “pre-built” environments in .NET. Those are Development, Staging and Production. We know this because we can use code like so :
env.IsDevelopment(); env.IsProduction(); env.IsEnvironment("Test1");//Checks the environment is Test1
Makes sense. And if we check the source code of .NET to see what these actually do :
public static bool IsDevelopment(this IHostingEnvironment hostingEnvironment) { if (hostingEnvironment == null) { throw new ArgumentNullException(nameof(hostingEnvironment)); } return hostingEnvironment.IsEnvironment(EnvironmentName.Development); }
Where Environment.Development is just a static const of “Development”. So under the hood, if we use env.IsDevelopment(), all we are really checking is that the Environment matches the string of “Development”.
Again, theoretically these are just helper methods so do they actually matter? At first I thought not but let’s keep going.
I noticed that for local development, the team was using “Local” as the environment, not “Development”. When I asked why? They mentioned that they normally name their first CD environment as “Development” instead.
At first, this seems like a non issue right? I mean a developer can name their environments however they like, and when we want to check our code for which environment we are in, we just need to say :
env.IsEnvironment("Local");
I thought it really doesn’t make much difference to us? But that’s us. What about the underlying .NET code written by Microsoft?
Framework IsDevelopment Checks
By this point, I was pretty sure I had things figured out, but I had to prove it. My first thought was, I know that Microsoft expects your local development to be called “Development” exactly. Not “Local”, not “Dev” not “DevMachine”, but “Development”. I know this because when we create a new ASP.NET Core web application, we get boilerplate code that looks like so :
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
So I went on the hunt through the .NET source code (Again, I cannot tell you how good it is to have .NET open sourced!), and found this :
bool isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment;
Following this trail, the setting of “ValidateScopes” to true is actually what triggers the check for our “Cannot consume scoped service from singleton” exception. And because our local development was called “Local” and our CD environment called “Development”, we only saw this trigger in CD and not on our developer machines! Bingo!
Obviously calling our CD environment “Development” was probably wrong, but I also think that there are a tonne of developers calling their local environment all sorts of things, without realizing that deep in the framework, there are indeed checks to see if the environment is explicitly called “Development” and to make checks accordingly.
In short, for local development on your machine, you should always use the exact environment name of “Development”.