Is there a way "out of the box" to have environment variables in appsettings.json values expanded automatically?
To take a contrived example:
{ ... "MyPath1": "C:/MyApp/%ENV%/Myfolder1/MyFile1.dat", "MyConnectionString": "Server=MyServer%ENV%..." ...
}My objective is to be able to switch my environment by changing a single environment variable, and have it affect multiple appsetting values, rather than having per-environment configuration files.
UPDATE
I've looked through the source code of JsonConfigurationProvider and as far as I can see there is no such feature out of the box.
I can see that it should be possible by deriving a custom class from JsonConfigurationProvider and overriding the Load() method:
public override void Load()
{ base.Load(); foreach(var key in Data.Keys) { Data[key] = Environment.ExpandEnvironmentVariables(key); }
}But I'm quite new to .NET Core configuration, which leads to a second question:
How do I get this custom implementation to replace the standard one? I.e. to remove the default appsettings.json and appsettings.environment.json providers and replace by the custom one. Presumably something to be added in Program.cs:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureAppConfiguration(config => { config.??? what do I need here? } ); 4 2 Answers
To create and use a custom configuration source, two implementations are needed:
It's the IConfigurationSource implementation that gets added to the IConfigurationBuilder's sources. This implementation is also responsible for creating its own IConfigurationProvider, which is reponsible for loading the data from the source.
Here's both a custom implementation of IConfigurationSource (ExpandJsonConfigurationSource) and IConfigurationProvider (ExpandJsonConfigurationProvider):
public class ExpandJsonConfigurationSource : JsonConfigurationSource
{ public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new ExpandJsonConfigurationProvider(this); }
}
public class ExpandJsonConfigurationProvider : JsonConfigurationProvider
{ public ExpandJsonConfigurationProvider(ExpandJsonConfigurationSource source) : base(source) { } public override void Load() { base.Load(); Data = Data.ToDictionary( x => x.Key, x => Environment.ExpandEnvironmentVariables(x.Value), StringComparer.OrdinalIgnoreCase); }
}There's not a lot going on here. These implementations just follow the same approach as the existing JsonConfigurationSource and JsonConfigurationProvider implementations, with custom processing for expanding the environment variables.
To replace the preconfigured JSON-based sources, use the following:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureAppConfiguration((ctx, builder) => { var jsonConfigurationSources = builder.Sources .OfType<JsonConfigurationSource>() .ToList(); foreach (var jsonConfigurationSource in jsonConfigurationSources) { var indexOfJsonConfigurationSource = builder.Sources .IndexOf(jsonConfigurationSource); builder.Sources.RemoveAt(indexOfJsonConfigurationSource); builder.Sources.Insert( indexOfJsonConfigurationSource, new ExpandJsonConfigurationSource { FileProvider = jsonConfigurationSource.FileProvider, Path = jsonConfigurationSource.Path, Optional = jsonConfigurationSource.Optional, ReloadOnChange = jsonConfigurationSource.ReloadOnChange }); } });The delegate passed in to ConfigureAppConfiguration does the following:
Locate all configured instances of
JsonConfigurationSource. There should be two instances: forappsettings.jsonand for e.g.appsettings.Development.json.For each instance:
Determine the position of the instance within the preconfigured sources.
Remove the instance.
Replace the removed instance with an instance of
ExpandJsonConfigurationSource, copying across the properties to ensure it has the same path, optional flag, etc.
I created a library to perform variable substitution, it substitutes from any configuration source, not just environment variables. See:
You just need to register it as another configuration source, this is done by calling .EnableSubstitutions() on the ConfigurationBuilder.
With your example:
- if in appsettings.json you have:
"MyPath": "C:/MyApp/%ENV%/Myfolder1/MyFile1.dat" - and you have the following environment variable:
ENV = Foo - then you should enable substitutions so:
.EnableSubstitutions("%", "%")Finally when requesting MyPath from the configuration you will get: C:/MyApp/Foo/Myfolder1/MyFile1.dat