If you are just starting your explorations in ASP.NET, you’ll inevitably come across a situation where you’ll need to connect to a database… or maybe it is a third-party API. Perhaps you’ll need to fire off an email and you need to connect to a mail server. In these situations, you may initially opt to save the connection information directly in your C# code but there are some issues with this approach that you should consider:
- Hard-coding secrets – API keys, usernames, passwords, etc. within source code is a big no-no. Such sensitive information will inevitably end up on GitHub or whatever source control system that you are using and available for all to see.
- You may have to connect to different systems in different environments. For example, you may need to connect to a test database server during development and testing and may need to connect to an entirely different system in production.
Thankfully, dotnet provides a very capable and flexible configuration system that will allow you to separate this environment-specific and/or secret information from your source code in an elegant fashion. Let’s look at how we can set it up and use it.
Configuration Providers
ASP.NET Configuration System provides you with a wide variety of options in terms of storing your configuration values and retrieve them during runtime:
Command-Line Arguments
Not sure how valuable these are in a production scenario, but you can certainly start up your dotnet app from the command line with one or more configuration parameters included in the command. Consider this simple dotnet app that echoes out a configuration item named MySecret
:
var builder = WebApplication.CreateBuilder(args); var mySecret = builder.Configuration.GetValue<string>("MySecret"); Console.WriteLine($"MySecret: {mySecret}"); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
Now, let’s run this app from the terminal, passing the config parameter along with the run command: dotnet run MySecret="Hello World"
The app will produce the following output:
Environment Variables
Your ASP.NET app is capable of reading environment variables, as well. Try it out by setting a new environment variable:
export SecretKey="Hello from Environment"
If you’re on Windows, enter set SecretKey="Hello from Environment"
command from the cmd
command-line window, instead.
Now, run the app above again but this time just do dotnet run
without any additional arguments. You should see the following output:
Secret Manager
Another configuration provider that you find in dotnet is the Secrets Manager
. Unlike the others, this approach is meant for local development, only. Although you can set this up manually, the easiest way to do this via the dotnet cli (or by utilizing the tooling in Visual Studio). Since not everyone is using Visual Studio, I’ll go over the CLI approach today. If you’re following along, navigate to the directory where you have the simple demo application from above, stored. Issue the following command to have dotnet setup a secrets.json
file for this project:
dotnet user-secrets init
When you run this command, dotnet does two things behind the scenes to setup your project with Secret Manager:
Firstly, it generates a random/unique UserSecretsId
for your project and saves it within your .csproj
file. Open your csproj file in a text editor and you’ll see something like below:
Secondly, it created a secrets.json
file in a location outside your project directory. This is intentional and by design. By creating it outside your project directory, you mitigate the risk of accidentally committing this secrets file (with potential sensitive information) into your source control repository. By default, this file is stored in ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
on a Linux or Mac machine. On Windows, you can find this file at %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
.
You can certainly navigate to this file and create your json structure that you can then pull in during the running of your application. Alternatively, you can use the dotnet cli to add/remove/update secrets.
dotnet user-secrets list | remove | set "MySecret" "Hello from secrets.json"
Run your app again. It should display output similar to this:
But wait a minute! That didn’t work. Instead of the app picking up the value from your secrets.json
file, it instead displayed the value of the Environment Variable with the same MySecret
name from the previous example! This demonstrates another important aspect of ASP.NET’s configuration system: It harvests configuration information from all different configuration sources. You do not have to stick to a single configuration source for a given application. But as demonstrated in the example above, there is a default order of precedence. Here they are from the highest priority to the lowest:
- Command line
- Environment Variables
- Secrets.json
- appsettings.Environment.json
- appsettings.json
So, based on this order of precedence, my “MySecret” environment variable trumped the “MySecret” secrets.json value that I had set. Go ahead and remove that environment variable and run the app again.
This time, it pulled the configuration value from the secrets.json
file.
appsettings.json and appsettings.environment.json
You can add these files to your project and ASP.NET will harvest values from within them, as well. They work identical to a secrets.json
file. But note that a plain-old appsettings.json
will have lesser priority than an environment specific appsettings.json file. For instance, if your application is running in the “production” environment, an appsettings.production.json
file will take precedence over a regular appsettings.json file. But how does ASP.NET know what environment your app is in? What are the distinct types of environment specifications available?
Your ASP.NET application determines the particular environment context it is running under by either the DOTNET_ENVIRONMENT
or the ASPNETCORE_ENVIRONMENT
environment variables. In a web context, the latter takes precedence. If these variables have not been set, the app will default to Production. The framework provides three environments out of the box: Development, Staging and Production. However, you can have as many environment specifications as needed as you can manually check and compare them during runtime.
Closing Remarks
We have barely scratched the surface of dotnet’s configuration system. In upcoming episodes, we can look at how we change some of the defaults, how to save complex and hierarchical data in configuration, how to add type-safety and other related aspects of this system. Stay tuned.