One of the things that is not so good in ASP.NET is Web.config. The problem with is that it mixes web server configuration with application configuration. ASP.NET Core has completely changed the way configuration is done, so here I will take a deep dive to configuration in ASP.NET Core.

If you create a new ASP.NET Core MVC app using Visual Studio, you'll get a configuration file called appsettings.json. The contents look like this:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

The fact that the file is called appsettings.json doesn't mean it has to always be called that. You can see that Startup.cs defines the configuration to be loaded at the top in the constructor:

var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
Configuration = builder.Build();

First it sets the base path for further files specified to be the application path. Then it does the following:

  1. Adds the appsettings.json file as a source. Note that it is configured optional so it is not required. Also, if the file changes, the configuration will be reloaded.
  2. Adds an optional environment-specific configuration. So you can have files like appsettings.Development.json and appsettings.Production.json etc. The VS project has an environment variable ASPNETCORE_ENVIRONMENT set to Development by default so it will load appsettings.Development.json also if you create one by default.
  3. Adds environment variables. I will write later about how you can specify settings in environment variables.

You should note that order is important. Settings in appsettings.json can be overridden by settings in an environment-specific configuration file. Those can be further overridden by environment variables.

Getting settings

There are a couple ways to get settings. One way would be to use the Configuration object in Startup.cs.

You can make the configuration available in your app globally through Dependency Injection by doing this in ConfigureServices in Startup.cs:

services.AddSingleton<IConfiguration>(Configuration);

Then you can write a constructor like this for example in a controller:

public HomeController(IConfiguration config)
{
}

Then, in Startup.cs and elsewhere you can access settings like this:

string setting = config["MySettings:SomeSetting"];

So what's up with the colons? Settings have a hierarchy. If we have a JSON configuration file like this:

{
  "MySettings": {
    "SomeSetting": "Test2",
    "Another": true
  }
}

We can access the settings like this:

string setting = config["MySettings:SomeSetting"];
bool another = bool.Parse(config["MySettings:Another"]);

So that is one way. Pretty much the same way we have accessed appSettings all this time. But there is another way, which is quite nice.

Options pattern

Instead of reading settings with keys, we can define our settings as a class. So if our settings file is like this:

{
  "MySettings": {
    "SomeSetting": "Test2",
    "Another": true
  }
}

We can define a class like this:

public class MySettings
{
    public string SomeSetting { get; set; }
    public bool Another { get; set; }
}

So how do we make this class map to the settings, and even better, become available for DI?

You have to add these two lines in ConfigureServices in Startup.cs:

services.AddOptions();
services.Configure<MySettings>(Configuration.GetSection("MySettings"));

Why GetSection("MySettings")? Because our settings are not in the root of the hierarchy. If they were, we would pass Configuration directly.

Now we can inject the settings like this:

public HomeController(IOptions<MySettings> config)
{
}

And use them like this:

string setting = config.Value.SomeSetting;
bool another = config.Value.Another;

If you want my opinion, use the Options pattern. It allows you to use a strongly typed object for settings.

Configuration sources

You already saw we can use JSON files as sources. But what else can we use and how?

XML files

To use XML files, you need to add this NuGet package: Microsoft.Extensions.Configuration.Xml. I added the 1.0.0 to my project.json and added a appsettings.xml file.

<configuration>
  <MySettings>
    <SomeSetting>Test</SomeSetting>
    <Another>true</Another>
  </MySettings>
</configuration>

I then added it as part of configuration like this:

.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true)

Quite easy no?

INI files

Same as XML, add NuGet Microsoft.Extensions.Configuration.Ini. Then you create a file such as:

[MySettings]
SomeSetting=TestIni
Another=true

Add it to the configuration chain like this:

.AddIniFile("appsettings.ini", optional: true, reloadOnChange: true)

Environment variables

Environment variables are great because they are not part of the code in any way, and thus developers can all have their own settings without affecting others. Azure Web Apps also provide app settings and connection strings as environment variables, so we can use them to override settings.

The NuGet package for this one is Microsoft.Extensions.Configuration.EnvironmentVariables by the way.

The default MVC project template adds environment variables to configuration with:

.AddEnvironmentVariables()

Now of course environment variables don't have a hierarchy, they are just key-value pairs. How can we set settings? Here are a couple examples:

MySettings:SomeSetting=Setting value
MySettings__Another=true
MySettings:MoreSettings:ThirdSetting=Some value

Note that you can either use the colon-syntax, or you can use a double-underscore instead of a colon if you need.

Command-line arguments

This can be quite useful, though not necessarily for ASP.NET Core. Actually, if you are running the app with IIS/IIS Express, you can't pass arguments. If you run it on dotnet-CLI, there is something you have to be careful about. The first argument passed is always the application DLL file name. The configuration system will blow up because of that. I'll show you how to get around that.

Anyway, the NuGet package is Microsoft.Extensions.Configuration.CommandLine.

You can then add the command-line arguments to configuration with:

.AddCommandLine(Environment.GetCommandLineArgs().Skip(1).ToArray())

Note the Skip(1). This way we avoid that DLL file name from going and messing up the configuration. In Visual Studio we can then define a command-line argument like this:

--mysettings:somesetting "Some value from commandline"

You can use the same colon-syntax here. Pretty sweet.

In-memory configuration

This one is for when you want to set settings at run-time. Could be used in tests for example.

Just define it like this:

.AddInMemoryCollection(new Dictionary<string, string>
{
    {"MySettings:SomeSetting", "MyValue" }
})

It takes an IEnumerable<KeyValuePair<string, string>>, I put in a Dictionary.

User secrets

This is quite an awesome new feature. There are two main scenarios:

  1. Storing settings you do not want to accidentally check in to source control (client secrets etc.)
  2. Storing settings that are developer-specific (database server connection strings etc.)

The second one has been a pain with current MVC.

The NuGet package for user secrets is Microsoft.Extensions.Configuration.UserSecrets. It is added by default to basic MVC templates in Visual Studio. The other part added by default is this in the Startup class' constructor:

if (env.IsDevelopment())
{
    builder = builder.AddUserSecrets();
}

We only want to use this secret store while we are in development. In Azure you might use something like Key Vault.

Note that while it is called a secret store, it really is a non-encrypted JSON file somewhere in your AppData folder. But the important thing is that it isn't in the project. Which means you can't accidentally check it in to source control.

The last part is that you need a userSecretsId. It's just an identifier for the app. With 1.0.0 it was stored in project.json at the root-level like this:

"userSecretsId": "aspnet-TestApp-20170103063931"

But starting with 1.0.1, you should add an assembly-level attribute called UserSecretsIdAttribute in your project (StackOverflow answer about this).

For example, in your Startup.cs at the top:

[assembly: UserSecretsId("aspnet-TestApp-20170103063931")]

You should also update the call in the constructor to this:

builder.AddUserSecrets<Startup>();

This will make it explicit that it will try to find the user secrets id from the assembly of the Startup class. The old call will still work, and in fact will still work with project.json as well. But as project.json is being deprecated, you should be using this method instead.

Managing secrets is really easy, just right-click on your project in Visual Studio and click on Manage User Secrets. This opens the JSON file containing the secrets for that app. You can then just edit them. In development mode they will be taken into the configuration as well, overriding and/or adding settings.

Azure App Service configuration

I do a lot of development on App Service. So of course I was curious how I can configure my Azure environment settings.

First the easy one, app settings.

Just make sure environment variables are a source that is defined after your configuration file. Then we can define an app setting:

  • Key: MySettings:SomeSetting
  • Value: Value from App Service

And it is switched live at runtime :)

Now I didn't mention connection strings yet. There's an extension method on Configuration called GetConnectionString(name). That is just shorthand for GetSection("ConnectionStrings")[name] though. But if we stick to convention, we define our connection strings like this:

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=localhost;Initial Catalog=DbName;Integrated Security=True"
  }
}

They can then be gotten from IConfiguration like this:

string connStr = configuration.GetConnectionString("DefaultConnection");

Now I published it to Azure again, and set a connection string like this:

  • Name: DefaultConnection
  • Value: Something=Else;
  • Type: SQL Database

I was seriously not expecting this to work. But it did! In real-time! This is awesome! :)

I checked the source code for fun on GitHub, you can find the line that made it work here. Kudos to the devs there!

So it really is quite easy to get environment-specific settings in Azure App Service.

Summary

  • Use whatever file format that you like
  • Create environment-specific configuration files if necessary
  • Use user secrets as a way to store values outside the source code (environment variables are another option for that)
  • Remember that order matters when building the configuration
  • Follow the Options pattern and create a strongly typed class for the configuration data
  • Define settings in Azure App Service's Application Settings if running on App Service

Also check the official documentation for the latest information on configuration.