How to read Configuration value from lowest level value - c#

I am trying to provide some configuration data for my application so that we have simple settings stored in the source control, but our deployment system will replace the Encryption Key in appsettings.json when it deploys.
For development I still want a general key, but for specific users in their secrets I would like to provide a value that they can be sure will be secure.
I have my configuration files setup like this:
appsettings.json
{
"SystemConfiguration" : {
"EncryptionKey" : "weak"
}
}
appsettings.Development.json
{
"SystemConfiguration" : {
"EncryptionKey" : "devweak"
}
}
In my user secrets
{
"SystemConfiguration" : {
"EncryptionKey" : "this is a secret"
}
}
In my controller construction I'm getting the injected IConfiguration configuration.
And then
public SysConfigController(IConfiguration configuration)
{
var result = configuration["SystemConfiguration:EncryptionKey"];
}
but the value of result is always "weak" unless the higher level settings files don't contain the value at all (: null) doesn't work either.
Is there a way that I can retrieve the lowest level value?

Seems your configuration registrations are wrong. The last registration which contains the specific key will be used.
For example in your Program.cs file (assuming you are using ASP.NET Core, otherwise Startup.cs's constructor for ASP.NET Core 1.x) you can override the registrations (or just add the ones you like when you use the default host builder method):
public static IWebHostBuilder BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddUserSecrets<Startup>()
.AddEnvironmentVariables()
.AddCommandLine(args);
})
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights();
In this example the following have been registered
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true):
Global configuration file
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
Enviornmentspecific configuration file, i.e. appsettings.Development.json
.AddUserSecrets<Startup>()
The user secrets
.AddEnvironmentVariables()
Environment variables
.AddCommandLine(args);
Command line parameters
When looking up for a key, the search happens in reverse order. If its defined as command line parameter, it will override all the other keys and values (from environment variable, user secrets, environment specific and global file).
So put the least important file at the beginning and most important (overriding) one at the end.
In this case if its defined in your user secrets, it will override the appsettings.Development.json and appsettings.json provided values.
From the docs
Configuration sources are read in the order that their configuration providers are specified at startup. The configuration providers described in this topic are described in alphabetical order, not in the order that your code may arrange them. Order configuration providers in your code to suit your priorities for the underlying configuration sources.
A typical sequence of configuration providers is:
Files (appsettings.json, appsettings..json, where is the app's current hosting environment)
User secrets (Secret Manager) (in the Development environment only)
Environment variables
Command-line arguments
It's a common practice to position the Command-line Configuration Provider last in a series of providers to allow command-line arguments to override configuration set by the other providers.

You can use method ConfigureServices(IServiceCollection services) in Startup.cs.
Describe your setting property in class and bind it. For example:
services.Configure<SystemConfiguration>(options => Configuration.GetSection("SystemConfiguration").Bind(options));
public class SystemConfiguration
{
public string EncryptionKey{get;set;}
}
Then, you can use DI for geting class
public class SomeClass
{
private readonly SystemConfiguration _systemConfiguration{get;set;}
public SomeClass (IOptions<ConfigExternalService> systemConfiguration)
{
_systemConfiguration = systemConfiguration;
}
}

Related

Read appsettings.json from Main project in DBContext Class library

I have a question. Is it possible to read the appsettings.json File in the DbContext class of another class library? I tried to write an AppSettingsService.cs to read the content of appsettings.json which I then injected into another Service where I need content from the appsettings.json File. But somehow it's not possible to inject a Service into the DbContext class. What I mean with impossible is that I cannot create Migrations and apply them to the Database then
What I'm expecting is: I want to read the appsettings from my main ASP.NET Web Api project in the DbContext class of a C# Class library. Also it would be great to choose which appsettings.json file is used depending on the profile. F.e. appsettings.Development.json, appsettings.Stage.json etc.
You can run update command and pass it the startup project (your ASP.NET Web API one) via -s or --startup-project (check out common options in the CLI reference doc). So from the root of the solution it can look like:
dotnet ef database update -p /PathToDbContextProj -s /PathToWebAPIProj
Or from the data project folder:
dotnet ef database update ../PathToWebAPIProj
You can certainly inject an instance into your class that inherits from DbContext, e.g.
public class MyDatabaseDataContext : DbContext
{
private readonly IConfiguration _configuration;
public MyDatabaseDataContext(DbContextOptions<MyDatabase> dataContextOptions,
IConfiguration configuration) : base(
dataContextOptions)
{
_configuration = configuration;
}
// Your DbSet declarations....etc
public DbSet<Type> MySet { get; set; }
}
In your host process, you can initialise IConfiguration as normal, read from whichever sources you like, which might look something like
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration((context, config) =>
{
config.Sources.Clear();
var env = context.HostingEnvironment;
config
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("appsettings.local.json", optional: true, true)
.AddEnvironmentVariables();
});
webBuilder.UseStartup<Startup>();
});
}
So, configure the reading of appsettings/env variables in the top level host process, make those setting values available on IConfiguration (or, a strongly typed object if you should wish) then inject that into your DbContext instance

Can we use a single appsettings.json file instead of maintaining multiple versions of appsettings.{environmentname}.json for different environments?

I'm using .NET 6 and I want to achieve 2 things:
Using a single appsettings.json file instead of maintaining multiple versions of appsettings.{environmentname}.json for different environments
Remove hard-coding from the appsettings.{environmentname}.json file to allow (1)
(Currently I'm hardcoding secrets like DB connection string, key vault configration details etc. for different environments in respective appsettings files which I want to remove and instead read the same secret values from Azure Key Vault where I'm already storing the same)
How can I achieve these?
Below code snippet is how I'm maintaining 2 appsettings.{environmentname}.json files(Development & Release):
var builder = WebApplication.CreateBuilder(args);
var hostingEnvironment = builder.Environment;
if (hostingEnvironment.IsDevelopment())
{
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.Development.json", false, true)
.AddEnvironmentVariables();
}
else
{
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.Release.json", false, true)
.AddEnvironmentVariables();
}
Note: I did some research and understood that for (1) we can probably do something like:
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable(
"ASPNETCORE_ENVIRONMENT")}.json", false, true)
.AddEnvironmentVariables();
But then, assuming the fact that the app would be automatically deployed every time leveraging CI/CD, can I set the value of ASPNETCORE_ENVIRONMENT in the pipeline?
As for, (2), I tried removing hardcoded secrets from appsettings.{environment}.json files and configured key vault like below but it doesn't work:
builder.Services.Configure<KeyVaultConfig>(options => builder.Configuration.GetSection("KeyVaultConfig").Bind(options));
if (!string.IsNullOrEmpty(builder.Configuration["KeyVaultConfig:KeyVaultUrl"]))
{
var credentials = new ClientSecretCredential(builtConfig["KeyVaultConfig:TenantID"], builtConfig["KeyVaultConfig:ClientID"], builtConfig["KeyVaultConfig:ClientKey"]);
var secretClient = new SecretClient(new Uri(builtConfig["KeyVaultConfig:KeyVaultUrl"]), credentials);
//builder.Configuration.AddAzureKeyVault...
}
If in the end you are going to use CI/CD you can only need 2 files (appsettings.json and appsettings.Development.json) and the default C# handling of these.
In the development one you hardcode all the configuration you need for dev.
In the standard one, you put keys that would be replaced during the CI/CD pipelines.
for example
in appsettings.development.json you have
"ConnectionStrings": {
"myDb": "Server=myDbServer;Database=myDb;User Id=userDev;Password=myPassword;"
}
in appsettings.json you have
"ConnectionStrings": {
"myDb": "#{connectionString}#"
}
And then replace "#{connectionString}#" with the environment value in the CI/CD pipelines configuration
If you really want to have only one file, you could try Json variable substitution like documented here documented here during the CI/CD pipelines, but the first approach allows you to handle secrets with vault more easily

random ConfigurationBuilder behavior

In a .net core application, why I am always in "production", even if the ASPNETCORE_ENVIRONMENT is 'development'?
class Program {
static void Main(string[] args) {
var builder = new ConfigurationBuilder();
BuildConfig(builder);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Build())
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
// ...
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
switch (serviceToUse) { // ... } })
.UseSerilog()
.Build();
IService svc;
// ... got the corresp service...
svc.Run();
}
static void BuildConfig(IConfigurationBuilder builder) {
string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
?? "Production";
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true)
.AddUserSecrets<Program>()
.AddEnvironmentVariables();
}
}
However, when I test
I get rather unexpected values in the test setting:
[13:36:55 ERR] Environment.GetEnvironmentVariable('ASPNETCORE_ENVIRONMENT')=Development
[13:36:55 ERR] Environment.GetEnvironmentVariable('DOTNET_ENVIRONMENT')=Development
[13:36:55 ERR] The test setting is 'test value from secrets.json';
having this in projet:
My question is why does it take the Production settings if I am in Development mode?
It’s unclear to me where you are retrieving the configuration using this._config.GetValue<string>("test"). My guess is that you are retrieving it from the IConfiguration injected via DI.
In that case, you should note that the Host.CreateDefaultBuilder will also already come with its own configuration setup which does not really care about what you are doing before with your BuildConfig.
The default host builder will already set up configuration using the files appsettings.json and appsettings.<env>.json (and others) and will register that configuration as the IConfiguration singleton that is being provided to your application. In order to register the environment-specific file, it will use the environment from the IHostEnvironment which is what decides what actual environment your application is running on.
With ASP.NET Core and the web host, it is possible to configure this environment using the ASPNETCORE_ENVIRONMENT environment variable. However, since you are not using ASP.NET Core but just the host builder on its own, this environment variable is not being used. Instead, the default host builder will look for the environment variable DOTNET_ENVIRONMENT.
Alternatively, you can also call hostBuilder.UseEnvironment to set the environment explicitly if you want to take it from a different environment variable.
Finally, when you are setting up your own configuration, I would suggest you to reconfigure your host so that it will use this configuration object itself instead of building its own (which may or may not match your desired setup). You can do that by calling hostBuilder.ConfigureAppConfiguration.

ASP.NET Core 2.1 application not getting correct appsettings.json

I have 2 applications
App
ClassLibrary
App references ClassLibrary and both have their own appsettings.json file. My class library app has no startup.cs or program.cs so I have a static class that is supposed to read from the local appsettings.json file like this:
static class ConfigurationManager
{
public static IConfiguration AppSetting { get; }
static ConfigurationManager()
{
AppSetting = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
}
}
This is the appsettings.json in the class library
{
"AccountsAddress": "http://localhost:55260/api/Accounts/"
}
The problem starts when my app runs, it calls a method located in class library. However, the appsettings that is called is not the one in class library. Its the one in the app itself. Is there a way to specify that I want to reference the appsettings in the class library instead of the App.
Is there a way to specify that I want to reference the appsettings in the class library instead of the App.
Rename class library's appsettings.json file to something unique to that library.
something like {classlibname}.appsettings.json
After renaming it, make sure to set it to be copied to output directory.
Also make sure after renaming it that the code is calling the new name
//...
.AddJsonFile("classlibname.appsettings.json", optional: true, reloadOnChange: true)
//...

Environment variables configuration in .NET Core

I'm using the .NET Core 1.1 in my API and am struggling with a problem:
I need to have two levels of configurations: appsettings.json and environment variables.
I want to use the DI for my configurations via IOptions.
I need environment variables to override appsettings.json values.
So I do it like this so far:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Something here
services.Configure<ConnectionConfiguration>(options =>
Configuration.GetSection("ConnectionStrings").Bind(options));
// Something there
}
With my appsettings.json composed like this
{
"ConnectionStrings": {
"ElasticSearchUrl": "http://localhost:4200",
"ElasticSearchIndexName": "myindex",
"PgSqlConnectionString": "blablabla"
}
}
I get all the configurations mapped to my class ConnectionConfiguration.cs. But I cannot get the environment variables to be mapped as well. I tried the names like: ConnectionStrings:ElasticSearchUrl, ElasticSearchUrl, even tried specifying the prefix to .AddEnvironmentVariables("ConnectionStrings") without any result.
How should I name the environment variables so it can be mapped with services.Configure<TConfiguration>()?
The : separator doesn't work with environment variable hierarchical keys on all platforms. __, the double underscore, is supported by all platforms and it is automatically replaced by a :
Try to name the environment variable like so ConnectionStrings__ElasticSearchUrl
Source
Have a look at ASP.NET Core Configuration with Environment Variables in IIS
To do the same with environment variables, you just separate the levels with a colon (:), such as HitchhikersGuideToTheGalaxy:MeaningOfLife.
In your case you'll use ConnectionStrings:ElasticSearchUrl as mentioned in your question.
Also notice the following:
When you deploy to IIS, however, things get a little trickier, and unfortunately the documentation on using configuration with IIS is lacking. You go into your server's system settings and configure all your environment variables. Then, you deploy your app to IIS, and it... explodes, because it's missing those necessary settings.
Turns out, in order to use environment variables with IIS, you need to edit the advanced settings for your App Pool. There, you'll find a setting called "Load User Profile". Set that to True. Then, recycle the App Pool to load in the environment variables. Note: you must do this even if your environment variables are added as "System variables", rather than "User variables".
I believe you're looking for this:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
I've 3 configurations
dev-final
dev-local
dev-test
And 4 *.json files
appsettings.json
appsettnigs.dev-final.json
appsettings.dev-local.json
appsettings.dev-test.json
appsettings.json holds global configuration values, and the other files specific ones.

Categories