14

When using the IConfigurationBuilder in a .NET Core 2.1 application with a Generic Host I configure 4 sources; but after the scope of ConfigureAppConfiguration there are 6 sources.

At some point 2 additional source I have already loaded are added a second time in an order that is causing appsettings.Environment.json values to be hidden. I have also tried removing the hostsettings.json configuration and verified that is not affecting this. This is for an Azure Webjob using WebjobsSDK 3.0 and .Net Core 2.1

    var builder = new HostBuilder()
        .ConfigureHostConfiguration(configurationBuilder =>
        {
             //This is to do some basic host configuration and should only add 2 sources
         configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
                configurationBuilder.AddJsonFile("hostsettings.json", optional: true);
                configurationBuilder.AddEnvironmentVariables(prefix: "APPSETTING_ASPNETCORE_");
            })
            .ConfigureAppConfiguration((hostContext, configurationBuilder) =>
            {
                //at this point there are 0 sources in the sources
                IHostingEnvironment env = hostContext.HostingEnvironment;
                configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
                configurationBuilder.AddJsonFile("appSettings.json", optional: false, reloadOnChange: true)
                    .AddJsonFile($"appSettings.{env.EnvironmentName}.json", optional: true,
                        reloadOnChange: true);
                configurationBuilder.AddEnvironmentVariables(prefix: "APPSETTING_ASPNETCORE_");
               //at this point there are 4 sources
            })
            .ConfigureServices((hostContext, servicesCollection) =>
            {
                //now there are 6, 2 additional source that are duplicates
                servicesCollection.Configure<IConfiguration>(hostContext.Configuration);

})

I expect a configuration provider with only the 4 sources, including the ChainedConfigSource, I have setup to be included. But 2 additional sources are added which are duplicates of the appsettings.json and the environment variables which I declared before loading the environment specific appsettings.environment.json.

Now when injected the into a class the appsettings.json settings were added last are returned over a appsettings.environment.json

Ryan Holsman
  • 361
  • 1
  • 2
  • 10

5 Answers5

12

It is possible to reorder builder.Sources to fit your needs. For example, here is an extension method that you can use to add a new JSON file after the last one, instead of at the very end:

public static class ConfigurationBuilderExtensions
{
  public static IConfigurationBuilder AddJsonFileAfterLastJsonFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
  {
    var jsonFileSource = new JsonConfigurationSource
    {
      FileProvider = null,
      Path = path,
      Optional = optional,
      ReloadOnChange = reloadOnChange
    };
    jsonFileSource.ResolveFileProvider();
    var lastJsonFileSource = builder.Sources.LastOrDefault(s => s is JsonConfigurationSource);
    var indexOfLastJsonFileSource = builder.Sources.IndexOf(lastJsonFileSource);
    builder.Sources.Insert(indexOfLastJsonFileSource == -1
      ? builder.Sources.Count
      : indexOfLastJsonFileSource + 1, jsonFileSource);
    return builder;
  }
}

You can then use it like this:

.ConfigureAppConfiguration((hostingContext, config) =>
{
    config.AddJsonFileAfterLastJsonFile(
      "appsettings.Custom.json",
      optional: true,
      reloadOnChange: false);
})

You can adapt this to your needs and insert it wherever you need it to be.

Christian Rondeau
  • 4,143
  • 5
  • 28
  • 46
  • you should github and nuget this. Very useful for keeping Environment vars and cmdline options as the last added options. – BozoJoe Mar 19 '22 at 23:09
11

But 2 additional sources are added which are duplicates of the appsettings.json and the environment variables

I had a similar issue with an Azure WebJob using the HostBuilder, and noticed that these 2 source were appended to the end of the list of config sources. This had undesirable results: development settings from appsettings.json overwrote the production settings fromappsettings.Production.json.

These additional sources appear to be added here by by ConfigureWebJobs.

The fix was to re-order the HostBuilder call chain so that the call to ConfigureWebJobs comes before the call to ConfigureAppConfiguration. These extra two sources are still present, but since they are now at the start of the list of configuration sources, and not at the end, they have no undesirable effects.

Anthony
  • 5,176
  • 6
  • 65
  • 87
  • 4
    This drove me nuts for 3 hours. Thanks. Now to find out why .bind() defaults to the appsettings.json instead of the appsettings.{environment}.json file.. – Hardrada Jan 03 '20 at 22:11
4

Reading the source code for the Hostbuilder.cs class you will see that the Configuration added in the AddHostingConfiguration gets added to the ApplicationConfiguration.

Let me show you, you can find the source at https://github.com/aspnet/AspNetCore/blob/1c3fa82908fe2cb773626b6613843213286a482b/src/Microsoft.Extensions.Hosting/HostBuilder.cs

The build call will first create the HostingConfiguration, then the AppConfiguration.

Here's the code that builds the HostingConfiguration

    private void BuildHostConfiguration()
    {
        var configBuilder = new ConfigurationBuilder();
        foreach (var buildAction in _configureHostConfigActions)
        {
            buildAction(configBuilder);
        }
        _hostConfiguration = configBuilder.Build();
     }

Now in the BuildAppConfiguration you will see that the HostingConfiguration gets added ontop of the AppConfiguration

    private void BuildAppConfiguration()
    {
        var configBuilder = new ConfigurationBuilder();
        //Here _hostConfiguration gets added ontop
        configBuilder.AddConfiguration(_hostConfiguration);
        foreach (var buildAction in _configureAppConfigActions)
        {
            buildAction(_hostBuilderContext, configBuilder);
        }
        _appConfiguration = configBuilder.Build();
        _hostBuilderContext.Configuration = _appConfiguration;
    }

Now the Build functions are private and there is no way to reset/clear sources from the builder.

If you dont want to implement your own version of the HostBuilder I'd suggest not seperating the Host settings from your Appsettings

Patrik Nyman
  • 73
  • 2
  • 11
  • I was considering this; I verified the sources in the built configuration are only referencing appsettings.json and none of them are pointed to hostsettings.json. After your suggestion I attempt to comment out the the line configurationBuilder.AddJsonFile("hostsettings.json", optional: true); and still had the same number of providers with 2 pointing the appsettings.json file. – Ryan Holsman Dec 21 '18 at 16:22
  • After reading the source file you submitted I see a the new ConfigurationBuilder is created, then the host config is added. Then each of actions you have setup are called in order. Would that mean that the most recent AppConfiguration would override the HostConfiguration? – Ryan Holsman Dec 21 '18 at 16:29
4

So according to the Documentation the WebHostBuilder loads the appSettings.json and appSettings.env.json files for you. But it does not say anything about the HostBuilder doing this as well, I believe this is due to a lack of documentation and I cannot determine where in the source code that is coming from.

To resolve this issue, I changed the way my configuration files are setup. Previously I had connection strings in the appSettings.json file as well as the appSettings.env.json files. So I was having an issue with the configuration files added last replacing the values from the base configuration file. I have now moved environment based settings only into the configuration files that are for each environment and only kept the settings that were global to all environments in the base configuration file.

Seems like old habits from the .NET framework configuration transformation set ups die hard. I cannot determine if the same key in a IConfiguration declared from multiple providers should be changed to the last loaded provider, I thought some documentation covered this and confirmed this but now I cannot find it.

Ryan Holsman
  • 361
  • 1
  • 2
  • 10
4

2023 Update

See sample .net 7 application.

Clear all config sources first, and then set them as you wish.

// Program.cs

var builder = WebApplication.CreateBuilder(args);

// Clear Config sources
builder.Configuration.Sources.Clear();

// Add config sources from lowest priority to highest
builder.Configuration.AddJsonFile("mysettings.json");
builder.Configuration.AddEnvironmentVariables();

// Build and run app
var app = builder.Build();
app.MapGet("/", () => "Hello");
app.Run();
Francisco Vilches
  • 3,426
  • 1
  • 15
  • 18