Adds support for placeholders in any configuration source (e.g. appsettings.json).
Install the ConfigurationPlaceholders package from NuGet:
Package manager:
Install-Package ConfigurationPlaceholdersOr via the .NET CLI
dotnet add package ConfigurationPlaceholdersYou can add ConfigurationPlaceholders to your project with the AddConfigurationPlaceholders extension method.
var builder = WebApplication.CreateBuilder( args );
builder
.AddConfigurationPlaceholders( new InMemoryPlaceholderResolver( new Dictionary<String, String?>
{
{ "FQDN", fullDomainName }
} ) ); This will replace the placeholder ${FQDN} with the fully qualified name of the machine running the application.
Placeholder in the appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"CertificateSubject": "${FQDN}"
}You can specify any number of placeholder resolvers IPlaceholderResolver in the call to AddConfigurationPlaceholders.
Later added placeholder resolvers IPlaceholderResolver will override values from before added placeholder resolvers IPlaceholderResolver.
By default, ConfigurationPlaceholders will check if values are provided for all placeholders. If there are any missing placeholder values, a ConfigurationPlaceholderMissingException will be thrown.
You can change this behavior by passing another MissingPlaceholderValueStrategy to AddConfigurationPlaceholders.
The following strategies are available:
Will check if values are provided for all placeholders. If there are any missing placeholder values, a ConfigurationPlaceholderMissingException will be thrown.
You can think of this one as a lazy execution version of VerifyAllAtStartup.
A ConfigurationPlaceholderMissingException will be thrown when a configuration entry with a missing placeholder value is being accessed. If the value never gets accessed, no exception will be thrown.
Placeholders for which no value is provided will be replaced with an empty string.
"Hello, ${MissingValue}" will result in "Hello, "
Placeholders for which no value is provided will not be replaced. The resulting value will still contain the placeholder.
"Hello, ${MissingValue}" will result in "Hello, ${MissingValue}"
You can find some examples using ConfigurationPlaceholders here
Resolves placeholder values from an in-memory lookup. Works similar to the AddInMemoryCollection configuration source.
new InMemoryPlaceholderResolver( new Dictionary<String, String?>
{
{ "ApplicationName", Assembly.GetExecutingAssembly().GetName().Name },
{ "ApplicationVersion", Assembly.GetExecutingAssembly().GetName().Version!.ToString() }
} )Resolves placeholder values by invoking user provided value factories.
new CallbackPlaceholderResolver( new Dictionary<String, Func<String?>>
{
{ "Time", () => DateTime.Now.ToString( "HH:mm:ss.fff" ) }
} )Searches for values in all configuration sources matching the placeholder key.
new ConfigurationPlaceholderResolver()In this example LocalDb is built based on other values in appsettings.json:
{
"Lookup": {
"DataDir": "X:/Temp/",
"DbDir": "${Lookup:DataDir}db/"
},
"LocalDb": "${Lookup:DbDir}store.db"
}Resolves placeholder values by searching for environment variables matching the placeholder key. The search is performed in this priority order:
- EnvironmentVariableTarget.Process
- EnvironmentVariableTarget.User
- EnvironmentVariableTarget.Machine
new EnvironmentVariableResolver()You can add your own placeholder resolvers by implementing IPlaceholderResolver.
Potential sources could be REST APIs, files, secret stores etc...
Different application setups require different ways to add ConfigurationPlaceholders.
var builder = WebApplication.CreateBuilder( args );
builder
.AddConfigurationPlaceholders( new InMemoryPlaceholderResolver( new Dictionary<String, String?>
{
{ "FQDN", fullDomainName }
} ) );Host
.CreateDefaultBuilder( args )
.AddConfigurationPlaceholders( new InMemoryPlaceholderResolver( new Dictionary<String, String?>
{
{ "FQDN", fullDomainName }
} ) )var configuration = new ConfigurationBuilder()
.AddJsonFile( "appsettings.json" )
....
.AddConfigurationPlaceholders( new List<IPlaceholderResolver>
{
new InMemoryPlaceholderResolver( new Dictionary<String, String?>
{
{
"ApplicationName", Assembly.GetExecutingAssembly().GetName().Name
}
}
} ),
new EnvironmentVariableResolver()
} )
.Build();You can reference values containing placeholders from placeholders...
{
"Lookup": {
"SinksNs": "Serilog.Sinks",
"DataDir": "X:/Temp/",
"LogDir": "${Lookup:DataDir}logs/",
"DbDir": "${Lookup:DataDir}db/"
},
"Serilog": {
"Using": [ "${Lookup:SinksNs}.Console", "${Lookup:SinksNs}.File" ],
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": { "path": "${Lookup:LogDir}${ApplicationName}/${ApplicationName}-${ApplicationVersion}.log" }
}
]
},
"Test": "Today is the ${Today} (${Day}) an it is ${Time} ${NoValueDefinedForThisOne}",
"LocalDb": "${Lookup:DbDir}store.db"
}In this example we can see several placeholders referencing values containing other placeholders.
E.g. ${Lookup:DbDir} will be resolved with the value ${Lookup:DataDir}db/ from Lookup:DbDir (using ConfigurationPlaceholderResolver). ${Lookup:DataDir} is another placeholder that will be replaced with the value of Lookup:DataDir => X:/Temp/.
You can combine values from multiple IPlaceholderResolver with multiple configuration sources.
ConfigurationPlaceholders natively supports IOptions<T>, IOptionsMonitor<T> and IOptionsSnapshot<T>. ConfigurationPlaceholders also plays nice with validation using ValidateDataAnnotations and ValidateOnStart.
The ModernWebApi example shows how to use these features together with ConfigurationPlaceholders.