Loading the Configuration
ArgentSea fully leverages the configuration architecture of .NET Core. In brief, ArgentSea requires one or more configuration files and offers a simple extension method to load the entire framework at start up.
About .NET Configuration
If the .NET Core configuration architecture is new to you, it essentially consists of two parts:
- A configuration Dictionary, which can be loaded from multiple sources — one of which is typically a file called appsettings.json
- An “Options” architecture, which casts the configuration entries into a strongly-typed configuration objects.
One of the key improvements of the configuration architecture in .NET standard is the dictionary architecture, which allows entries to be loaded and combined from multiple sources. So, for example, you might load the account names from an appsettings.json configuration file, the passwords from a secrets.json file (or Key Vault or Secrets Manager), and the server names from environment variables. Properly managed, this can make deployments both easier and more secure.
The previous examples all used JSON for configuration. This is not a requirement. In non-JSON configuration contexts, like environment variables, you can specify these values as key-value pairs. The key concatenates the hierarchy separated by “:”. Arrays and lists should include an index.
The same configuration information listed above can be stored and loaded as key-value pairs. An example of the same shard set configuration in non-JSON format would be:
Key | Value |
---|---|
SqlShardSets:0:ShardSetName | Primary |
SqlShardSets:0:DataSource | DbServer1 |
SqlShardSets:0:FailoverPartner | Mirror1 |
SqlShardSets:0:UserName | webUser |
SqlShardSets:0:Password | pwd1234 |
SqlShardSets:0:Shards:0:ShardId | 0 |
SqlShardSets:0:Shards:0:InitialCatalog | ShardDb1 |
SqlShardSets:0:Shards:0:ReadConnection:ApplicationIntent | ReadOnly |
SqlShardSets:0:Shards:0:ReadConnection:DataSource | Mirror1 |
SqlShardSets:0:Shards:1:ShardId | 1 |
SqlShardSets:0:Shards:1:InitialCatalog | ShardDb2 |
SqlShardSets:0:Shards:1:ReadConnection:ApplicationIntent | ReadOnly |
SqlShardSets:0:Shards:1:ReadConnection:DataSource | Mirror1 |
Because .NET Core’s configuration architecture allows values to be aggregated from multiple data stores, we can create configuration entries where they can be most conveniently managed. Gone are the days of needing to transform configuration files upon deployment (unless you really like that type of thing).
Typically, there are three types of configuration entries:
- Secure values, like passwords or keys, which should not be readily accessible.
- Environment-specific values, like server names, which change as releases are promoted through various environments.
- Application values, which specify how the application should consistently behave.
This capability is critical for managing configuration outside of the application. Using key-value pairs allows configuration values to be hosted in environment variables and secure stores.
Securing Passwords
In a development environment, you should consider using the UserSecrets functionality, which prevents this information from being checked into your source code repository.
UserSecrets uses a JSON file, so the password entries can be simply removed from the appsettings.json file and stored in the secrets.json file. Note that the object counts must be consistent between your appsettings.json file and the secrets.json file.
User Secrets
{
"SqlDbConnections": [
{
},
{
},
{
"ReadConnection": {
"Password": "pwd1234"
},
"WriteConnection": {
"Password": "pwd5678"
}
}
]
}
In other environments, you might consider using AWS Secrets Manager, Azure Key Vault, Docker secrets, a secure file share, or something similar.
Most of these secrets stores store key value pairs. The .NET configuration manager resolves the hierarchical JSON files with a semi-colon, “:”, separator between the property hierarchy. Array elements are referenced with an index value.
Tip
Systems that don’t support semi-colon separators in their keys (AWS Secrets Manager, for example) can use double underscores (“__”) instead.
Consequently, the previous JSON values could be saved as corresponding key-value pairs as:
Key | Value |
---|---|
SqlDbConnections:2:ReadConnection:Password | pwd1234 |
SqlDbConnections:2:WriteConnection:Password | pwd5678 |
In reality, most implementations would have a single secure password used for every shard connection, in which case the key in your secrets store is simply:
Key | Value |
---|---|
SqlShardSets:0:Password | pwd1234 |
Environment-specific Configuration
Managing configuration through multiple staging and release environments works the same way. You can store environment-specific settings — like server names or database names — in server environment variables. These are also key-value pairs, so the .NET Core configuration hierarchy would be rendered to keys in the same way as described in the previous section.
The database configuration JSON presented previously would be saved in environment variables as:
Key | Value |
---|---|
SqlDbConnections:0:DataSource | DbServer1 |
SqlDbConnections:0:InitialCatalog | MainDb |
SqlDbConnections:1:DataSource | DbServer1 |
SqlDbConnections:1:InitialCatalog | OtherDb |
SqlDbConnections:1:WriteConnection:DataSource | DbServer2 |
SqlDbConnections:1:ReadConnection:DataSource | DbServer3 |
SqlDbConnections:1:ReadConnection:InitialCatalog | Db1 |
SqlDbConnections:1:WriteConnection:DataSource | DbServer4 |
SqlDbConnections:1:WriteConnection:InitialCatalog | Db2 |
Note
The order of configuration loading matters. Your Startup class should load appsettings.json first. When the environment variables are subsequently loaded, any existing values in appsettings.json will be overwritten.
ArgentSea uses the configuration dictionary built into .NET Core, so you can use any compatible configuration provider — including files, command line arguments, databases, and more.
There are no restrictions on which configuration entries belong to which providers (data sources). You can store passwords in environment variables or even command arguments, if you want to. Pick the right platform to management your data effectively, ArgentSea can use it as long as the values are consolidated correctly into the configuration dictionary.
Configuration Options
The next phase in .NET Core’s configuration processing is the Options rendering. This converts the aggregated dictionary entries into strongly typed configuration objects, called Options. ArgentSea uses these typed Options objects to build its ShardSets and Databases services.
Unfortunately, a misconfigured property or entry can cause the Options class to be null when a value was expected. This can be difficult to debug, as there are often no error messages, just a null result. You can debug the Options classes during startup to see which values are unexpectedly null. Experiment with removing configuration values until the Options classes render as expected.
The JSON object hierarchy and property types should exactly match those of the Options objects, so if you have any doubts, explore the Options classes using C# or other strongly typed language.
Loading the Configuration
ArgentSea uses .NET Core’s built-in Options configuration and dependency injection architecture. The complexity of turning a JSON configuration file into a connection object is as simple as calling an extension method in the ConfigureServices
method of your Startup
class.
public void ConfigureServices(IServiceCollection services)
{
...
// add your injectable logging provider
services.AddLogging();
// add the ArgentSea SQL database connections
services.AddSqlServices(Configuration);
// now add your custom data classes, which use the data components
services.AddSingleton<MyDataStore>();
...
services.AddMvc();
...
}
This code references a Configuration
property. It is common practice to obtain the configuration object from the constructor of the Startup
class, then use this to set the Configuration
property.