Search code examples
.net-coreconsole-application.net-5appsettings

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


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?


Solution

  • 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.