How to Configure an ASP.NET Core Application?

This post will walk you through configuration in ASP.NET Core. We’ll look at how to define different configuration sources, read their configuration keys, and how the framework handles these various configuration sources. We’ll also show you some use cases for ASP.NET Core’s configuration key support.
Reading Configuration Settings From appsettings.json
We’ll create an ASP.NET Core API project for all of our manipulations.
dotnet new mvc -n Configuration.AspNet.Core.Lab
After creating the application, let’s try and read our first configuration. To do this, let’s add the string-key custom configuration with the value-from-appsettings.json value to the appsettings.json file as follows:
{ "AllowedHosts": "*", "string-key": "value-from-appsettings.json" // new custom configuration }
So that we can read the new configuration, we add a console.WriteLine() with the configuration key to the Program.cs, as follows:
var builder = WebApplication.CreateBuilder(args); // Configuration should be available here IConfiguration configuration = builder.Configuration; System.Console.WriteLine("string-key => " + configuration["string-key"]); // Some code here ... app.Run();
The builder.Configuration object provides access to the configuration key we added using the Configuration[‘MyKeyName’] getter.
After starting the app, we can see the following result in the console return:
Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:04.16 Building... string-key => value-from-appsettings.json
So, there you have it! We just created and read a new configuration with ASP.NET Core!
But how does this mechanism work exactly? This is what we’ll look at in the next.
How Does Configuration Work in ASP.NET Core?
The CreateBuilder method is called in the Program.cs:
var builder = WebApplication.CreateBuilder(args);
It registers the ASP.NET Core default configuration sources by calling the CreateDefaultBuilder method in the framework. This method initializes various providers, which are fed by the configuration sources.
The provider is an object that makes it possible to read configuration keys from the source. Once initialized, the provider is added to a set of providers in the ConfigurationManager (see Builder.Configuration). The code used to initialize the list of providers is shown below:
By default, the configuration sources are added to the ConfigurationManager in this order:
1. AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
Initializes and adds a provider with the configuration keys from the appsettings.json file (Point A). The optional parameter indicates that the provider won’t be added if the file doesn’t need to be present. Finally, reloadOnChange lets you reinitialize the provider if the appsettings.json file changes.
2. AddJsonFile($"{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
Initializes and adds a provider with the configuration keys from the appsettings.{env.EnvironmentName}.json file (Point B). The ASPNETCORE_ENVIRONMENT environment variable in the ./Properties/launchSettings.json file defines the value of env.EnvironmentName. We’ll revisit this in the next section.
3. AddUserSecrets(appAssembly, optional: true)
Initializes and adds a provider with the configuration keys from the User secret (Point C): we’ll expand on this concept later.
4. AddEnvironmentVariables()
Initializes and adds a provider with the configuration keys from the environment variables (Point D). We’ll look closely at how this works.
5. AddCommandLine(args)
Initializes and adds a provider with the configuration keys from the command arguments (Point E). These are the arguments that are passed when the dotnet run –arg1 command is executed, allowing you to launch your application.
When all the ConfigurationManager providers have been initialized, you can use the following getter to read the configuration keys:
IConfiguration[“TheKeyIAmLookingFor” ];
This getter will fetch the configuration key value as shown below.
This diagram shows how ASP.NET Core searches for a configuration key.
When searching for a key value, the ConfigurationManager asks the last provider on its list of providers if it has the key value. If so, it returns the value. If not, it forwards the request to the provider before it on the list.
From that point on, the configuration key values will be read using the Last In First Out (LIFO) principle. The only one used is the latest value for a key added in the ConfigurationManager via the provider. This is how the framework prioritizes its configuration key values.
Let’s put this reading in priority order into practice, starting with the environment appsettings.
Reading a Configuration From the Environment Appsettings
If you look at the project structure, you will see that there are two files: appsettings.json and appsettings.Development.json. As seen earlier, the priority of the configuration providers means that the configurations defined in appsettings.Development.json will take precedence over those in appsettings.json.
To illustrate this, we add the same configuration in appsettings.Development.json, changing the value, and we add new, different configuration types.
{ "string-key": "value-from-appsettings.Development.json", "bool-key": false, "int-key": 22, "array-string": ["array-dev-value1", "array-dev-value2"], }
Let’s also add the missing key readings to Program.cs, as shown below.
var builder = WebApplication.CreateBuilder(args); // Configuration should be available here IConfiguration configuration = builder.Configuration; System.Console.WriteLine("env => " + configuration["ASPNETCORE_ENVIRONMENT"]); System.Console.WriteLine("string-key => " + configuration["string-key"]); System.Console.WriteLine("bool-key => " + configuration["bool-key"]); System.Console.WriteLine("int-key => " + configuration["int-key"]); System.Console.WriteLine("array-string (first item) => " + builder.Configuration["array-string:0"]); System.Console.WriteLine("array-string (second item) => " + builder.Configuration["array-string:1"]); // Some code here ... app.Run();
The following result shows that even though the string-key key is present in both appsettings.json and appsettings.Development.json, only the value defined in appsettings.Development.json is used. The same is true for the other keys we added.
Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:04.16 Building... env => .Development string-key => value-from-appsettings.Development.json bool-key => False int-key => 22 array-string (first item) => array-dev-value1 array-string (second item) => array-dev-value2
Try it out in a different development environment (production or similar). Don’t forget to create the relevant JSON file.
This configuration type is extremely useful for simulating the application’s behavior in a variety of different environments (development, production, etc.). For example, when using an in-memory database, displaying logs during debugging, or even with Trace, if necessary, during development.
Note: it’s good practice when configuring your application to declare all your configuration keys in the appsettings.json file with default values and to change the desired key value in the appropriate environment file. This makes it easier to read the application and understand how it works. Let’s look at an example where your app uses a third-party service: the live URL for that service will be defined in the appsettings.json file, and the URL of the service’s other environments, or a mock, will be defined in appsettings.Development.json.
Reading a Configuration From the User Secrets
The user secret is a way to store sensitive configurations on your local machine during development using a Command Line Interface (CLI) tool called user-secrets. The managed secrets are stored in a location away from the project using them, meaning they are not source-controlled. In addition, the secrets can be shared between multiple projects by referencing them in “.csproj.”
By default, ASP.NET Core only initializes a provider with user secrets in a development environment (see Point C of the code for adding providers).
⚠️ Caution: the configuration stored in user secrets is not encrypted. It should only be used for development purposes.
How User Secrets Works via the CLI
Run the command below in your project directory.
dotnet user-secrets init
The user-secrets Command Line Interface (CLI) adds the UserSecretsId key to YourProjectName.csproj. This is a dotnet that lets you manage secrets locally. The UserSecretsId Global Unique Identifier (GUID) identifies the secret.json file on your machine where the secrets will be saved. Below is an example of a csproj where user secrets have already been initialized.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <RootNamespace>MyProject</RootNamespace> <UserSecretsId>0084a804-ad71-4a3b-ba35-783bc3018666</UserSecretsId> </PropertyGroup> </Project>
The file that contains the secrets is in the following directory:
- %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secret.json (in Windows)
- ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json (in Linux)
So, we can add a configuration to secret.json, as shown in the commands below:
dotnet user-secrets set string-key "value-from-secrets.json" dotnet user-secrets set bool-key false dotnet user-secrets set int-key 33 dotnet user-secrets set "array-string:0" "array-value1-from-secrets " dotnet user-secrets set "array-string:1" "array-value2-from-secrets " dotnet user-secrets set "object-key:key1" "value1-from-secrets" dotnet user-secrets set "object-key:key2" "value2-from-secrets"
The commands above initialize the configuration keys as follows:
- string-key with the string value-from-secrets.json
- bool-key with the Boolean false
- int-key with the number 33
- array-string:0 the string array-value1 as the first item
- array-string:1 with the string array-value2 as the second item
- object-key:key1 with the string value1-from-secrets
- object-key:key2 with the string value2-from-secrets
Now that we’ve entered our user secrets, let’s look at the content of the secrets.json file.
{ "string-key": "value-from-secrets.json", "object-key:key2": "value2-from-secrets", "object-key:key1": "value1-from-secrets", "int-key": "33", "bool-key": "false", "array-string:0": "array-value1-from-secrets", "array-string:1": "array-value2-from-secrets", }
You will see that all configurations are stored flattened or in a key-value format. This is because the CLI can only handle the configurations in key-value format. However, you can store configurations by section, like in the appsettings.json file (see Logs section), by manually editing the file.
This type of configuration is a great way to store external service identifiers or encryption keys during development without putting them in the source code.
Note: You can access a file directly in Visual Studio by right-clicking the project file (.csproj) and then clicking Manage User Secrets.
Reading the User Secrets Configuration
Let’s take the project we created earlier and initialize user secrets. Then, we’ll add the following lines to Program.cs to read the configuration keys we stored in our secrets.json:
Console.WriteLine("Read 'string-key' from Program.cs => " + builder.Configuration["string-key"]); Console.WriteLine("Read 'bool-key' from Program.cs => " + builder.Configuration["bool-key"]); Console.WriteLine("Read 'int-key' from Program.cs => " + builder.Configuration["int-key"]); Console.WriteLine("Read 'array-string' from Program.cs (first item) => " + builder.Configuration["array-string:0"]); Console.WriteLine("Read 'array-string' from Program.cs (second item) => " + builder.Configuration["array-string:1"]); System.Console.WriteLine("key1 in section object-key from Program.cs => " + builder.Configuration["object-key:key1"]); System.Console.WriteLine("key1 in section object-key from Program.cs => " + builder.Configuration["object-key:key2"]);
When the application is run in development, we’ll see the values we initialized earlier using the CLI in the console return.
Read 'string-key' from Program.cs => value-from-secrets.json Read 'bool-key' from Program.cs => false Read 'int-key' from Program.cs => 33 Read 'array-string' from Program.cs (first item) => array-value1-from-secrets Read 'array-string' from Program.cs (second item) => array-value2-from-secrets key1 in section object-key from Program.cs => value1-from-secrets key1 in section object-key from Program.cs => value2-from-secrets
We can see that value-from-secrets.json is the value of the string-key. However, this key has already been defined in the appsettings.json and appsettings.Development.json files.
Here, we can see that the user secrets configuration keys take priority over the keys from the different appsettings files in the development environment.
Reading a Configuration from the Environment Variables
Moving on, let’s try reading a configuration from the environment variables. The string-key is in our appsettings.json file.
{ "string-key": "value-from-appsettings.json", }
Let’s try and read the values from the environment variables.
Initializing the Environment Variable
Type the following commands into a PowerShell Core terminal to initialize and read your environment variable:
${env:string-key} = "value-from-environment" ${env:bool-key} = true ${env:int-key} = 44 ${env:array-string__0} = "array-value1-from-environment"
Alternatively, you can initialize your environment variable manually in Windows, as described below:
1. Open the System Properties window and go to the Environment Variables Control Panel:
2. Create a new environment variable
3. Check that it has been created
Restart the application.
Building... env => Development Read 'string-key' from Program.cs => value-from-environment Read 'bool-key' from Program.cs => true Read 'int-key' from Program.cs => 44 Read 'array- string ' from Program.cs (first item) => array-value1-from-environment Read 'array-string' from Program.cs (second item) => array-value2-from-secrets key1 in section object-key from Program.cs => value1-from-secrets key1 in section object-key from Program.cs => value2-from-secrets
We can see that the configuration keys stored in the environment variables have now been applied. Also, we can see from the values of string-key, bool-key, int-key, and array-string__0 that they have priority over the values defined in the user secrets. The object-key value is taken from the user secrets because isn’t initialized in the environment variables.
The configuration keys defined in the environment variables are handy for defining a configuration that is specific to the machine running the application. The connectionStrings with the database is a typical example.
Note: You may have to restart your integrated development environment (IDE) for the environment variable to take effect.
Also, note that to access the values of keys associated with an array or object, we use the “:” separator in the ConfigurationManager getter to read the values. However, this separator is not supported when defining environment variables. Instead, we need to replace it with “__”: two consecutive underscores.
Reading a Configuration from the Command Arguments
The idea here is simple: declare what you are reading by running the dotnet run command with our configuration as an argument.
dotnet run string-key="value-from-command-line" array-key:1: "array-value-from-command-line"
The result is as follows:
Building... env => Development Read 'string-key' from Program.cs => value-from-command-line Read 'bool-key' from Program.cs => true Read 'int-key' from Program.cs => 44 Read 'array- string ' from Program.cs (first item) => array-value-from- environment Read 'array-string' from Program.cs (second item) => array-value2-from-command-line key1 in section object-key from Program.cs => value1-from-secrets key1 in section object-key from Program.cs => value2-from-secrets
We can see that the keys declared in the dotnet run command take priority over any keys declared in other sources. This shows that the configuration keys declared as command arguments have the highest priority.
This lets you force a configuration key’s value and change the application’s behavior without touching the binaries or the deployed configuration. Changing the application log level for an investigation in a running environment (dev, test, pre-prod) is a typical use case.
Reading a Complex Configuration
When talking about a complex configuration, we use the word “section” to refer to a logical grouping. In our example, we grouped the previous configurations into an Example section. This way of arranging configurations is similar to an object structure.
{ "Example": { "MyString": "value-from-appsettings.json", "MyBool": true, "MyInt": 22, "MyArray": ["array-value1", "array-value2", "array-value3"], } }
Direct Reading
Each value is read by prefixing the key with the section name followed by the “:” (colon) separator.
var builder = WebApplication.CreateBuilder(args); // Configuration should be available here Console.WriteLine("Read 'MyString' from Program.cs => " + builder.Configuration["Example:MyString"]); Console.WriteLine("Read 'MyBool' from Program.cs => " + builder.Configuration["Example:MyBool"]); Console.WriteLine("Read 'MyInt' from Program.cs => " + builder.Configuration["Example:MyInt"]); Console.WriteLine("Read 'MyArray' from Program.cs (first item) => " + builder.Configuration["Example:MyArray:0"]); Console.WriteLine("Read 'MyArray' from Program.cs (second item) => " + builder.Configuration["Example:MyArray:1"]); Console.WriteLine("Read 'MyArray' from Program.cs (third item) => " + builder.Configuration["Example:MyArray:2"]); // Add services to the container. builder.Services.AddControllersWithViews();
This is the result:
Building... Read 'MyString' from Program.cs => value-from-appsettings.json Read 'MyBool' from Program.cs => False Read 'MyInt' from Program.cs => 22 Read 'MyArray' from Program.cs (first item) => array-value1 Read 'MyArray' from Program.cs (second item) => array-value2 Read 'MyArray' from Program.cs (third item) => array-value3
We can see that all of the read values are strings.
Reading via an Object
Our complex configuration can be strongly typed into an object with the same structure (matching type and property name). Here, we create an object called ComplexConfigurationOptions, that will be linked to the configuration.
public class ComplexConfigurationOptions { public const string SectionName = "Example"; public string MyString { get; set; } public bool MyBool { get; set; } public int MyInt { get; set; } public string[] MyArray { get; set; } }
NB: It’s good practice to add the name of the configuration section to which the object should be copied.
Manually Linking the Configuration to an Object
Let’s try using the Bind() method of IConfigurationSection to initialize our ComplexConfigurationOptions object using the values from the Example section.
var myOption = new ComplexConfigurationOptions(); builder.Configuration.GetSection("Example").Bind(myOption); Console.WriteLine("Read 'string-key' from Option => {0}", myOption.MyString); Console.WriteLine("Read 'bool-key' from Option => {0}", myOption.MyBool); Console.WriteLine("Read 'int-key' from Option => {0}", myOption.MyInt); Console.WriteLine("Read 'array-string' from Option (first item) => {0}", myOption.MyArray[0]); Console.WriteLine("Read 'array-string' from Option (second item) => {0}", myOption.MyArray[1]); Console.WriteLine("Read 'array-string' from Option (third item) => {0}", myOption.MyArray[2]);
The console shows that all properties with the same name as a configuration key have been initialized with their corresponding values.
Read 'string-key' from Option => value-from-appsettings.json Read 'bool-key' from Option => False Read 'int-key' from Option => 22 Read 'array-string' from Option (first item) => array-value1 Read 'array-string' from Option (second item) => array-value2 Read 'array-string' from Option (third item) => array-value3
The binding means that our entire configuration is stored in an object, which is much easier to work with than strings. However, we can improve this even more by using the “options pattern.”
The Options Pattern
The options pattern lets you strongly type configurations in an object. It allows you to group configurations logically and independently.
builder.Services.Configure<ComplexConfigurationOptions>(builder.Configuration.GetSection(ComplexConfigurationOptions.SectionName));
The Microsoft.Extensions.Options.ConfigurationExtensions NuGet package contains the Configure<>() method. This is already included in ASP.NET Core by default.
public class HomeController : Controller { private readonly ComplexConfigurationOptions _options; public HomeController(IOptions<ComplexConfigurationOptions> options) { _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); Console.WriteLine("Read 'MyString' from options => " + _options.MyString); Console.WriteLine("Read 'MyBool' from options => " + _options.MyBool); Console.WriteLine("Read 'MyInt' from options => " + _options.MyInt); Console.WriteLine("Read 'MyArray' from options => " + string.Join(", ", _options.MyArray)); } }
The IOptions<T> interface can be used to access the configurations stored in the ComplexConfigurationOptions object. The latter is registered in the dependency manager and injected by the framework.
Read 'MyString' from options => value-from-appsettings.json Read 'MyBool' from options => False Read 'MyInt' from options => 22 Read 'MyArray' from options => array-value1, array-value2, array-value3
Learning More About ASP.NET Core
We’ve given you an overview of how ASP.NET Core configuration works. We’ve seen how the framework takes the configuration sources into account by default thanks to the provider initialization for each configuration source and how to read the values. We also looked at how and when priority is shown when reading the configuration keys.
Finally, we looked at strong typing of configurations using manual binding and the options pattern, which makes using configuration keys in code easier and more consistent.
We recommend reading the following documentation for further information: