DevOps extensions, used for application configuration setup.
Below is an example of loading various config sources using the UseDefaultConfigs
method. This will load the following configs (in the order shown):
- Environment Variables
- Command Line
appSettings.json
file- environment specific
appSettings.json
public class Program
{
...
public void ConfigureAppConfiguration(IConfigurationBuilder builder)
{
// Load config sources by calling the UseDefaultConfigs.
builder.UseDefaultConfigs();
// Load other config here...
}
...
}
Individual secrets can be loaded into configuration, directly from Key Vault in the following way:
public class Program
{
...
public void ConfigureAppConfiguration(IConfigurationBuilder builder)
{
// Load various config sources.
builder.UseDefaultConfigs();
// Pass the name of the secrets you wish to load into the configuration builder.
builder.AddKeyVaultSecrets("TenantId",
"SubscriptionId",
"OtherSecretName");
}
...
}
To use this method, the code expects a setting called "KEYVAULT_URL". This will typically have been setup by DevOps as an environment variable on the machine running the code. If we take a key vault instance called example1, the environment setting would look like this:
https://example1.vault.azure.net/
Otherwise, if the KEYVAULT_URL
setting cannot be found, the method will fallback to a config setting called "KeyVaultInstanceName". This can be set in your AppSettings.json
file. It would be look like example1
and the url will be inferred automatically.
AppSettings.json
{
"KeyVaultInstanceName": "example1",
"SomeOtherSetting1": 100,
"SomeOtherSetting2": false
}
If you require settings to be loaded from multiple Key Vaults, it can be done in the following way:
public class Program
{
...
public void ConfigureAppConfiguration(IConfigurationBuilder builder)
{
// Load from all config sources.
builder.UseDefaultConfigs();
// Overload method 1: Add from key vault loaded in KEYVAULT_URL setting.
builder.AddKeyVaultSecrets("SomeKey1", "SomeKey2");
var kvUriInstance1 = new Uri("https://instance1.vault.azure.net");
var kvUriInstance2 = new Uri("https://instance2.vault.azure.net");
// Overload method 2: Pass the instance and list of the secrets you wish to load into configuration.
builder.AddKeyVaultSecrets(kvUriInstance1, new [] {
"TenantId",
"SubscriptionId",
"OtherSecretName",
"connectionstrings--servicebus" });
builder.AddKeyVaultSecrets(kvUriInstance2, new [] {
"OtherKey1",
"OtherKey2",
"OtherKey3" });
}
...
}
For convenience, you can choose to map your Key Vault secrets to other config names by passing a Dictionary (key is secret name, value is mapped name) instead of array of string (secret names), as follows:
public class Program
{
...
public void ConfigureAppConfiguration(IConfigurationBuilder builder)
{
// Load various config sources.
builder.UseDefaultConfigs();
// Pass the name of the secrets you wish to load into the configuration builder.
builder.AddKeyVaultSecrets(new Dictionary<string, string> {
{ "TenandId","MyClass:MyTenantId" },
{ "SubscriptionId","MyClass:SubClass1:MySubId" },
{ "OtherSecretName","OtherSecretName" }
});
// Optional overload - you can pass the specific KV url if needed.
}
...
}
Take this POCO class:
public class AppSettings {
public string TenantId { get; set; }
public string SubscriptionId { get; set; }
public string OtherSecretName { get; set; }
public ConnStrings ConnectionStrings { get; set; }
}
public class ConnStrings {
public string ServiceBus { get; set; }
public string Cosmos { get; set; }
}
We can bind the settings to this class using the BindBaseSection
call as follows:
// Taken from Startup.cs after the ConfigureAppConfiguration above has been run.
public void ConfigureServices(IServiceCollection services)
{
// Example of binding settings directly to a class (without the "GetSection" call).
var appSettings = _configuration.BindBaseSection<AppSettings>();
// Example of directly using the settings directly after they are loaded.
var tenantId = _configuration["TenantId"];
var otherSecret = null;
if (_configuration.TryGetValue<string>("OtherSecretName"), out otherSecret)
{
... do something conditional if the setting exists ...
}
}
It's worth noting, if you have a Key Vault setting called with dashes in the name, ex "My-Key-1", it can bind to a Poco class property called "MyKey1". The BindBaseSection
method will make a version of the config setting that has not got the dashes.
Take this POCO class:
public class CustomSettings {
[KeyVaultSecretName("my-kv-key")]
public string CosmosKey { get; set; }
[KeyVaultSecretName("my-kv-url")]
public string CosmosUrl { get; set; }
public string DbConnectionString { get; set; }
}
We can bind two of the properties of this class to Key Vault secrets using the BindSection
call as follows.
The third will be taken directly from the specified section.
// Taken from Startup.cs after the ConfigureAppConfiguration above has been run.
public void ConfigureServices(IServiceCollection services)
{
// Example of binding settings directly to a class (without the "GetSection" call).
var appSettings = _configuration.BindSection<CustomSettings>("MySection");
}
If the section is not specified then it will attempt to load from KeyVault only
public void ConfigureServices(IServiceCollection services)
{
// Example of binding settings directly to a class (without the "GetSection" call).
var appSettings = _configuration.BindSection<CustomSettings>();
}
If you do not have control of the POCO that is being bound to, then additional mappings can be specified
public void ConfigureServices(IServiceCollection services)
{
// Example of binding settings directly to a class (without the "GetSection" call).
var appSettings = _configuration.BindSection<CustomSettings>("MySection",
c => c.AddMapping(c => c.DbConnectionString, "db-connectionstring"));
}
This approach does not require initial Key Vault loading using AddKeyVaultSecrets
Example of using with a WebHostBuilder when bootstrapping a Web Application.
public class Program
{
public static void Main(string[] args)
{
try
{
// Build and run the web host.
var host = CreateWebHostBuilder(args).Build().Run();
}
catch (Exception e)
{
// Probably want to log this using BigBrother (there's a bit of a
// race condition here as BB might not be wired up yet!).
// Catch startup errors and bare minimum log to console or event log.
Console.WriteLine($"Problem occured during startup of {Assembly.GetExecutingAssembly().GetName().Name}");
Console.WriteLine(e);
// Stop the application by continuing to throw the exception.
throw;
}
}
// In program, we setup our configuration in the standard microsoft way...
private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config => {
// Import default configurations (env vars, command line args, appSettings.json etc).
config.UseDefaultConfigs();
// Load config from key vault.
config.AddKeyVaultSecrets("TenantId",
"SubscriptionId",
"OtherSecretName",
"connectionstrings--cosmos",
"connectionstrings--servicebus");
})
.ConfigureLogging((context, logging) => {
// Add logging configuration and loggers.
logging.AddConfiguration(context.Configuration).AddConsole().AddDebug();
})
.UseStartup<Startup>();
...
}
public class Startup
{
private readonly ILogger<Startup> _logger;
private readonly IConfiguration _configuration;
// We can then grab IConfiguration from the constructor, to use in our startup file as follows:
public Startup(IConfiguration configuration, ILogger<Startup> logger)
{
_configuration = configuration;
_logger = logger;
}
public void ConfigureServices(IServiceCollection services)
{
// You could bind directly to a poco class of your choice.
_appSettings = _configuration.BindBaseSection<AppSettings>();
// Other setting bindings...
var bb = BigBrother.CreateDefault(_telemetrySettings.InstrumentationKey, _telemetrySettings.InternalKey);
_configuration.GetSection("Telemetry").Bind(_telemetrySettings);
_configuration.GetSection("HttpCors").Bind(_corsSettings);
_configuration.GetSection("RefreshingTokenProviderSettings").Bind(_refreshingTokenProviderOptions);
_configuration.GetSection("Endpoints").Bind(_endpoints);
}
...
}
All of the eshopworld.* packages are published to a public NuGet feed. To consume this on your local development machine, please add the following feed to your feed sources in Visual Studio: https://eshopworld.myget.org/F/github-dev/api/v3/index.json
For help setting up packages, follow this article: https://docs.microsoft.com/en-us/vsts/package/nuget/consume?view=vsts