Photo by Alexey Ruban on Unsplash
A common consideration for working with an ASP.NET Core application is how to store and retrieve values in configuration settings, as well as how to change and update them as the application moves to higher environments. It's common to need sensitive values like passwords, API keys, and database connection strings in order to call out to other services, and these settings must be stored separately from the application's source code. To help manage this process, ASP.NET Core contains a set of configuration providers for retrieving and storing values, as well as a class to make complex settings available in the application as strongly-typed objects.
In this tutorial, I'll use an extremely simple web API to illustrate the different configuration providers and how one overrides the next as the application moves to different environments. We'll start with some boilerplate code, create a class to hold configuration settings, then load those settings from different locations in the file system. Finally, we'll move the application to a Docker container to show how settings can be updated in different environments.
Prerequisites
I'll assume you have some familiarity with ASP.NET Core applications, but this article will still stay at a beginner level. To build a copy of the example application and follow along, you will need:
- Version 3.1 of the .NET Core SDK running on your development machine.
- Docker Desktop running on your local machine. The version I'm using as of this writing is 19.03.12.
- Postman, or an API tool of your choice, is recommended for making calls to the API and inspecting the results.
- Lastly, to follow along with the examples, I've created a Github repo with a before and after branch. The following command will clone the repo:
$ git clone https://github.com/jsheridanwells/aspnetcore-config-example.git
To fetch the before and after branches, run:
$ git fetch origin before:before
$ git fetch origin after:after
Then, $ git checkout <branch name>
will take you to before or after.
Lastly, I tend to prefer writing .NET Core tutorials as OS-agnostically as possible, so I'll be using the command line with Bash for most of the steps, rather than Visual Studio. If you are using an IDE like Visual Studio or the Windows command line, there may be some differences from what you'll see here.
The boilerplate
Here are the important files we'll be working with in this project:
-
appsettings.json
andappsettings.Development.json
are two configuration files provided by ASP.NET core for configuration settings in different environments. The first file,appsettings.json
contains any default settings, then anyappsettings.{Environment Name}.json
files override those settings. Since these files are stored at the root of the project directory, they should not contain any sensitive keys as these settings will usually checked into the project's version control system. -
Program.cs
is the main file that scaffolds an ASP.NET Core application and creates both the application and the host environment for the application. In this project, you'll notice a method called.ConfigureAppConfiguration
that is commented out. Later in this tutorial, we'll uncomment the method and play around with the directives in there. -
Startup.cs
defines all the top-level settings for the application including configuration, dependency in injection, and the middlewares used by the application. In this class, we'll define the entry point for the app's configuration. -
Controllers/ConfigController.cs
is the single controller in the project with a single method calledGetConfig
that will be modified to return the values in our settings so that we can inspect the configuration values that are available in the application. - Lastly,
Dockerfile
will be used to build the application in a container and experiment with passing in configuration values that way. Note that we won't discuss containerizing applications in this tutorial, but you won't need to know much Docker for this demo.
If you run the project using the CLI...
$ dotnet run
... and make a GET request to http://localhost:5000/config
, you should get "nothing implemented yet"
as your response.
Loading configuration in Startup.cs
Best practice for loading configuration settings is to create a simple class to map the configuration values, then adding them in the Startup
class.
In this demo, our configuration will consist of two keys: Id
will hold a Guid
that we can pretend is a secret value like an API key, and Location
where we'll write the location of the configuration object (e.g., appSettings
, user secrets, environment variables etc.). At the root of the project, create a file called MyConfig.cs
and add the following C# code:
using System;
namespace AspNetCoreConfigExample
{
public class MyConfig
{
public Guid Id { get; set; }
public string Location { get; set; }
}
}
Now, whenever we need these values, we can reference the section as "MyConfig"
. In appsettings.json
, the MyConfig
section is set as follows:
"MyConfig": {
"Id": "4c3c066c-928f-4de4-86b8-09365aed6a7c",
"Location": "from appSettings.json"
}
In the Startup
class, in the ConfigureServices
method, we'll update the method as follows:
// Add configuration section here
var configs = Configuration.GetSection("MyConfig");
services.Configure<MyConfig>(configs);
Configuration.GetSection
checks the configuration providers for any key named "MyConfig", then tries to map them to the MyConfig
class.
You can check that the project compiles by running $ dotnet build
.
Next, open ./Controllers/ConfigController.cs
.
To make the configuration values available to the class, we'll add an IOptions<T>
argument to the class constructor, and make the value of IOptions
a private key.
private MyConfig _configs;
public ConfigController(IOptions<MyConfig> opts)
{
_configs = opts.Value;
}
Finally, we'll update the GetConfig
method to return whatever is in the _configs
property:
[HttpGet]
public IActionResult GetConfig()
{
// 3. return the config to inspect it
var configs = _configs;
return Ok(new { Result = "config values: ", configs });
}
Now, run the application using the .NET CLI:
$ dotnet run
Make a GET request to http://localhost:5000/config
, and the response should come back as follows:
{
"id": "4c3c066c-928f-4de4-86b8-09365aed6a7c",
"location": "from appSettings.json"
}
From the above steps, we see how configuration is loaded through the Startup
class, then made available to the application through the IOptions<T>
class.
In the next steps, we'll add new configurations in different locations to demonstrate the hierarchy of providers in ASP.NET Core and how one provider can override the next.
The provider hierarchy
The default configuration setup for an ASP.NET Core app is hidden away in the Program
class, in a method called .ConfigureWebHostDefaults
. By default, the method checks for settings in a hierarchy of locations and if a matching configuration is found after a preceding one, settings from the latter are used.
According to this Microsoft blog, this is the order that providers are used:
- The
appsettings.json
file - Any
appsettings.{ ENVIRONMENT_NAME }.json
files - User secrets from the .NET User Secrets Manager
- Environment variables
- Command line arguments
All of this happens under the hood, allowing us to write the configuration code once in Startup
without having to add conditions for different environments. In the next few sections, we'll go through each of these providers, and at the end I'll show to override the default provider order for custom scenarios.
appsettings.json
When we ran the application in the last step, the configuration values that were returned came from the ./appsettings.json
file. This can be overridden with an appsettings.{ ENVIRONMENT NAME }.json
file. Add a file at the root of the project called appsettings.Development.json
and paste in the following:
{
"MyConfig": {
"Id": "6e20863b-ae31-4e6c-8711-00177586bec0",
"Location": "from appSettings.development.json"
}
}
Run the server again and make another get request to http://localhost:5000/config
and the response will now show the values from the Development
settings, which is the default environment when running an ASP.NET Core app from a local host. This is useful for storing connection strings to local databases or any generic configuration values that can be safely stored in a source control repository.
To see how a different environment can override these settings, create another files called appsettings.Staging.json
and paste the following:
{
"MyConfig": {
"Id": "bd304170-b899-42a9-9f9f-048e6e67a5dd",
"Location": "from appSettings.Staging.json"
}
}
We'll change the hosting environment to "Staging", then run set the application to run with that profile:
$ export ASPNETCORE_ENVIRONMENT="Staging"
$ dotnet run --launch-profile "Staging"
If you are using Windows, the first command is:
> set ASPNETCORE_ENVIRONMENT="Staging"
Now the request returns the settings from the Staging configuration.
User Secrets Manager
appsettings.json
is okay for generic settings, but some other settings for development need to be stored separately from the source code. These could be API keys, passwords, settings for cloud infrastructure, or connections strings for remote databases. The .NET CLI provides a utility called the User Secrets Manager that can store and load values from a local development filesystem.
If you are using Visual Studio, you can right-click the name of the project in the Solution Explorer to get a user secrets dialog that makes managing these values automatic.
Using the .NET CLI, there are a few steps:
- At the root of the project, initialize the User Secrets Manager:
$ dotnet user-secrets init
. - The project will need a user secrets ID which can be any string. This is added to the .csproj file. For this project, there is already a GUID added:
<!--AspNetCoreConfigExample.csproj-->
<PropertyGroup>
<!-- ...-->
<UserSecretsId>fa9be5ec-53e4-4275-8941-99cf83d8c344</UserSecretsId>
</PropertyGroup>
- Using the cli, you can add new values for the
Id
andLocation
settings for the project:
$ dotnet user-secrets set MyConfig:Id fa3e83d5-27e2-4a02-ae75-ad3763985290
$ dotnet user-secrets set MyConfig:Location 'User Secrets Store'
Now, if you navigate to ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
on your file system, you'll see a json file added that will map to the configuration settings as before. (Note: if you're using Windows, the path to the user secrets file is %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
).
Run the application, make a request to /config
again, and the values added with the User Secrets Manager have overridden the values from appsettings.json
without having to modify any of the code.
Note that the User Secrets Manager is only for development purposes and should not be used in a production scenario since those values are stored on the host in plain text.
Environment variables, command line arguments, and container commands
We can also set MyConfig:Id
and MyConfig:Location
as environment variables and they will take precedent over the user secrets settings.
If you are using Linux or OSX, you can add these as environment variables:
$ export MyConfig__Id=52a8f62b-43fa-4da6-8a7e-7b788402aeed
$ export MyConfig__Location="Environment Variable"
In Windows, the equivalent is:
> set MyConfig:Id=52a8f62b-43fa-4da6-8a7e-7b788402aeed
> set MyConfig:Location="Environment Variable"
Once again, /config
returns the environment variable settings.
We can also run the application with command-line arguments...
$ dotnet run MyConfig:Id=be4209bc-8162-4c77-9064-8dd912b2a413 MyConfig:Location='command line'
... and get the same result. These could be very useful when scripting the program in different environments like a CI/CD pipeline.
Where this really comes in handy is when building the application as a container image. In the next few steps, we'll Docker-ize the application three different ways. You won't need any deep Docker knowledge to do this as long as you have Docker Desktop installed and running on your development machine (check $ docker --version
to be sure).
The AspNetCoreConfigExample project contains a simple Dockerfile
that is basically the example provided in the Docker Documentation. It basically copies the code from the root directory and builds the executables, then moves the executables to a smaller environment with the .NET runtime to run the application.
On lines 17 and 18 of the Dockerfile
, new environment variables are set which will be passed to the application from the container host:
ENV MyConfig__Id='73ce1157-c1b4-4d90-8669-2b33c86d7801'
ENV MyConfig__Location='from Dockerfile'
Use the following command to build an image from the Dockerfile
:
$ docker build -t config-app .
Then run the image as a container on port :5000
:
$ docker run -it -p 5000:5000 config-app
The /config
response should now show values from the Dockerfile
.
Lastly, a Docker container can be run with variables as command-line arguments to also keep these values separate from source code (or a container repository).
Run the container again with this command:
$ docker run -it -p 5000:5000 \
-e MyConfig__Id='2021b838-6876-44b9-b123-1f760dc98aaa' \
-e MyConfig__Location='from Docker CLI' \
config-app
...and now you have settings from the command line.
Setting the configuration provider hierarchy
The default order of configuration providers that get called is logical for most situations, but ASP.NET Core also contains a scaffolding method for overriding the default order.
In this application, now that there are configuration settings stored in the user secrets file, those will be used over the asppsettings
files. We can instead specify that we want appsettings.json
to be used instead of user secrets.
In the sample project, in Program.cs
there are five lines commented out that call a method called ConfigureAppConfiguration
which takes a configuration builder as its second argument. The builder contains several methods for calling on configuration providers, and the settings from the last method to be called will override any previous settings. If you uncomment the lines you'll see the following:
.ConfigureAppConfiguration((hostContext, config) =>
{
config.AddUserSecrets<MyConfig>();
config.AddJsonFile("appsettings.json");
})
Run the application, and you'll see that the appsettings
configuration gets returned. If you change the order of those methods, or add any of the other providers, different configuration settings will be given preference.
Conclusion
In this project, we saw some of the configuration providers that are available in an ASP.NET Core application and how those settings can be moved around and concealed as the application is deployed to higher environments. This gives several convenient strategies for setting up the application for a variety of deployment types.
Top comments (0)