.Net Core 5 Console App, appsettings not loading for environment when done in Program.Main
Asked Answered
A

3

7

I want my .Net Core 5 console application to select settings from the appropriate appsettings file based on the DOTNET_ENVIRONMENT environment variable. I'm testing this by running it in the Visual Studio 2019 debugger and fetching the environment from my launchSettings.json file.

In a .Net Core 5 console application I have 4 "appsettings" files:

  • appsettings.json
  • appsettings.Development.json
  • appsettings.Staging.json
  • appsettings.Production.json

Each file Properties is set to Build Action : Content, and Copy to Output Directory: Copy if newer.

In my launchSettings.json I have my environment set to "Staging" like so:

 {
  "profiles": {
    "MyAppName": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "environmentVariables": {
        "DOTNET_ENVIRONMENT": "Staging"
      }
    }
  }
}

I need access to my configuration in the "Main" method in Program.cs, so in that class I am setting a module-level string variable "_environment" like so in the static constructor:

_environment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");

This works; the value "Staging" gets loaded into the variable _environment.

I then load my Configuration into a static variable like so: (EDIT--this was my mistake, assuming this static property loaded AFTER the static ctor. In fact it loaded BEFORE the static ctor. This meant the _environment variable was not set, which means my environment-specific appsettings file never loaded).

private static IConfiguration Configuration { get; } = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{_environment}.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables()
    .Build();

When I then examine the contents of the Configuration, I see that it is only loading values from appsettings.json. It is not loading values from appsettings.Staging.json. The specific value I am looking for is "ConnectionStrings". This is how the ConnectionStrings section looks in appsettings.json:

"ConnectionStrings": {
    "ConnectionStringName": "Data Source=SqlDevelopment; Initial Catalog=MyTable; Integrated Security=SSPI;",
  }

And this is how that same section looks in appsettings.Staging.json:

"ConnectionStrings": {
    "ConnectionStringName": "Data Source=SqlStaging; Initial Catalog=MyTable; Integrated Security=SSPI;",
  }

But when I read the DataSource from the Configuration it is always "SqlDevelopment", even though the environment is Staging.

After trying and failing, I tried loading these 4 Nuget packages, but it had no effect:

  1. Microsoft.Extensions.Configuration
  2. Microsoft.Extensions.Configuration.Binder
  3. Microsoft.Extensions.Configuration.EnvironmentVariables
  4. Microsoft.Extensions.Configuration.Json

What am I missing?

Antibaryon answered 11/11, 2021 at 15:38 Comment(0)
A
4

Thank you everyone and especially @CodeCaster for helping me.

The issue is that the _environment variable was an empty string when the static Configuration was set. I assumed that since I was setting it in the static constructor it was available, but the static ctor was actually running after I set my static Configuration, so _environment was an empty string, so the "Staging" configuration was never loaded.

I altered the code so that there is no chance that the runtime will set variables in an order that I did not expect:

    private static IConfiguration Configuration { get; } = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: true)
        .Build();

A number of posters essentially told me "you're doing it wrong." I realize that the runtime provides a dependency-injected Configuration after Host.CreateDefaultBuilder() is called. For reasons outside the scope of this question I happen to need Configuration before that call occurs, inside Program.Main. If I did not need Configuration in Program.Main, before the call to Host.CreateDefaultBuilder, none of this would be necessary.

Antibaryon answered 11/11, 2021 at 17:6 Comment(1)
You could use just Host.CreateDefaultBuilder(args).Build(); and gotten the same behavior, along with the logging and DI configurationsExcoriation
E
8

Console applications check the DOTNET_ environment variables, not the ASPNETCORE_ variables. This isn't a new change. It goes back at least to .NET Core 3.1. You need to set DOTNET_ENVIRONMENT instead.

ASP.NET Core applications use any environment variables prefixed with ASPNETCORE_ in addition to the DOTNET_ variables.

From the docs :

The default configuration loads environment variables and command line arguments prefixed with DOTNET_ and ASPNETCORE_. The DOTNET_ and ASPNETCORE_ prefixes are used by ASP.NET Core for host and app configuration, but not for user configuration. For more information on host and app configuration, see .NET Generic Host.

PS: Just 10 minutes ago I had the same problem and realized I had set DOTNETCORE_ENVIRONMENT instead of DOTNET_ENVIRONMENT

Excoriation answered 11/11, 2021 at 16:23 Comment(6)
This does not work. I have tried using both ASPNETCORE_ENVIRONMENT and DOTNET_ENVIRONMENT. Neither works. I'm missing something else.Antibaryon
Did you actually set the environment variables? Those are OS- or terminal-level settings, not lines in launchsettings.json. That file is only used by the IDE to set the environment variables when launching a process. When you run your application outside the IDE that file isn't usedExcoriation
As for I then load my Configuration into a static variable like so: why do that? That's (almost) the default behavior.Excoriation
Because I need access to the Configuration in my Program.Main class. And I am using launchSettings.json because I am running the application in my debugger.Antibaryon
@TomRegan I already explained that's the default behavior. When you use Host.CreateDefaultBuilder(args) the default behavior is to read the settings files (including the environment file), environment variables and the command line arguments.Excoriation
My code and approach was fine, except for the location where I set the _environment string variable from my launchSettings.json. Once I moved that into the static ctor it worked properly.Antibaryon
A
4

Thank you everyone and especially @CodeCaster for helping me.

The issue is that the _environment variable was an empty string when the static Configuration was set. I assumed that since I was setting it in the static constructor it was available, but the static ctor was actually running after I set my static Configuration, so _environment was an empty string, so the "Staging" configuration was never loaded.

I altered the code so that there is no chance that the runtime will set variables in an order that I did not expect:

    private static IConfiguration Configuration { get; } = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: true)
        .Build();

A number of posters essentially told me "you're doing it wrong." I realize that the runtime provides a dependency-injected Configuration after Host.CreateDefaultBuilder() is called. For reasons outside the scope of this question I happen to need Configuration before that call occurs, inside Program.Main. If I did not need Configuration in Program.Main, before the call to Host.CreateDefaultBuilder, none of this would be necessary.

Antibaryon answered 11/11, 2021 at 17:6 Comment(1)
You could use just Host.CreateDefaultBuilder(args).Build(); and gotten the same behavior, along with the logging and DI configurationsExcoriation
D
2

I need access to my configuration in the "Main" method in Program.cs, so in that class I am setting a module-level string variable "_environment" like so in the static constructor:

_environment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");

That's not how it works. You need to use IHostEnvironment.EnvironmentName. If you need access to your configuration in your Main() method, you're doing something wrong, and reading the environment variable is the wrong way to read the (ASP).NET Core Environment name.

The "(ASP).NET Core Environment name" can be set in multiple ways, one way being OS Environment variables. But they don't have to be, they can also be provided through command line arguments.

IHostEnvironment.EnvironmentName is the proper way to get the environment name.

Disappearance answered 11/11, 2021 at 16:39 Comment(5)
my question is, what I am doing wrong when I explicitly load both appsettings files into my Configuration class? There is no IHostEnvironment available in Program.Main, but I'm explicitly fetching the environment name from my launchSettings.json and explicitly loading appsettings.Staging.json. I've followed this exact same approach in a .Net core web application (fetch my configuration explicitly in Program.Main) and it works properly.Antibaryon
Your actual staging or production deployment won't have a launchSettings.json (that's meant for development time only), so that's the first thing you're doing wrong. But you're right, if your code passes .AddJsonFile($"appsettings.{_environment}.json", ...) with an _environment value of "Staging", then the contents of "appsettings.Staging.json" should override those of "appsettings.json". If not, then you didn't actually deploy appsettings.Staging.json, or you have a typo in either its filename or the values "ConnectionStrings" or "ConnectionStringName".Disappearance
I'm not worried about deployment, I just want to run it in my debugger, thus my fetching the environment name from launchSettings.json. I'm not getting the override, that is the problem. There is no typo.Antibaryon
Sure, you can claim there's no typo, but the Microsoft Configuration extensions aren't broken either, or they would be for a lot of projects. It's up to you to figure out why it isn't working, and to provide us with enough information to help you figure it out. What if you change the code to only .AddJsonFile($"appsettings.{_environment}.json", optional: false)? Does it run? Can it find the file? Can it parse its contents? Does the content actually contain the keys you expect it to? Obviously the value "ConnectionStringName" was edited before posting, for starters.Disappearance
thank you! Your suggestion to set optional: false is what I needed to find the problem! Not a typo, but a real hair-puller. I'll post the answer below.Antibaryon

© 2022 - 2024 — McMap. All rights reserved.