Here is my situation -- my development team must get its connection strings and other sensitive values like passwords from machine-level environment variables on Windows servers. I have a .NET 6 Console App using Generic Host, and in my appsettings.json file, I store the name of the environment variable I want to retrieve the value for, and I have a class that holds the environment variable name and the actual secret value that gets obtained from the environment variable.
My problem is that after I get the value from the environment variable and store it in TopSecretConfig.SecretValue, the value is accessible from from IOptions on my machine, whether I'm in Visual Studio 2022 or running the application from the command line. However, when it's deployed to our QA environment, the value is null when I access it from IOptions.
Obviously, the code works (on my machine), but is that the proper way to alter the configuration object and have the values bound so they are properly injected via IOptions later?
What environmental factors would prevent the code from working in another environment? The environment variables are present in the QA environment at the machine level.
The code below is not my actual code, but very close.
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"TopSecretConfig": {
"EnvironmentVariableName": "VARIABLE_NAME"
}
}
Class to hold config value:
public class TopSecretConfig
{
public string EnvironmentVariableName { get; set; }
public string SecretValue { get; set; }
}
Generic host snippet to get config:
using (var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging((context, builder) =>
{
// logging config
})
.ConfigureAppConfiguration((context, appConfig) =>
{
appConfig.Sources.Clear();
appConfig.SetBasePath(context.HostingEnvironment.ContentRootPath);
appConfig.AddJsonFile("appSettings.json");
appConfig.AddEnvironmentVariables();
appConfig.AddEnvironmentVariables("DOTNET_");
appConfig.AddEnvironmentVariables("TOP_SECRET_");
})
.ConfigureServices((builder, services) =>
{
services.Configure<TopSecretConfig>(builder.Configuration.GetSection(nameof(TopSecretConfig)));
// --------- Is this allowed? -----------
services.Configure<TopSecretConfig>(config =>
{
config.SecretValue = builder.Configuration[config.EnvironmentVariableName];
});
services.AddSingleton<Processor>();
})
.Build())
{
await host.StartAsync();
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
var processor = host.Services.GetRequiredService<Processor>();
await processor.ExecuteAsync();
lifetime.StopApplication();
await host.WaitForShutdownAsync();
}
Class that uses config:
public class Processor
{
private readonly ILogger<Processor> Logger;
private readonly TopSecretConfig SecretConfig;
public Processor(ILogger<TopSecretProcessor> logger, IOptions<TopSecretConfig> secretConfig)
{
this.Logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.SecretConfig = secretConfig?.Value ?? throw new ArgumentNullException(nameof(secretConfig));
// this.SecretConfig.SecretValue is null here.
}
}