Configuration

By Steve Smith and Daniel Roth

ASP.NET 5 supports a variety of different configuration options. Application configuration data can come from files using built-in support for JSON, XML, and INI formats, as well as from environment variables. You can also write your own custom configuration provider.

In this article:

Download sample from GitHub.

Getting and setting configuration settings

ASP.NET 5’s configuration system has been re-architected from previous versions of ASP.NET, which relied on System.Configuration and XML configuration files like web.config. The new configuration model provides streamlined access to key/value based settings that can be retrieved from a variety of providers. Applications and frameworks can then access configured settings using the new Options pattern

To work with settings in your ASP.NET application, it is recommended that you only instantiate an instance of Configuration in your application’s Startup class. Then, use the Options pattern to access individual settings.

At its simplest, the Configuration class is just a collection of Providers, which provide the ability to read and write name/value pairs. You must configure at least one provider in order for Configuration to function correctly. The following sample shows how to test working with Configuration as a key/value store:

1
2
3
4
5
6
7
8
9
// assumes using Microsoft.Framework.ConfigurationModel is specified
var builder = new ConfigurationBuilder();
builder.Add(new MemoryConfigurationProvider());
var config = builder.Build();
config.Set("somekey", "somevalue");

// do some other work

string setting2 = config["somekey"]; // also returns "somevalue"

Note

You must set at least one configuration provider.

It’s not unusual to store configuration values in a hierarchical structure, especially when using external files (e.g. JSON, XML, INI). In this case, configuration values can be retrieved using a : separated key, starting from the root of the hierarchy. For example, consider the following appsettings.json file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "Data": {
    "DefaultConnection": {
      "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-WebApplication1-8479b9ce-7b8f-4402-9616-0843bc642f09;Trusted_Connection=True;MultipleActiveResultSets=true"
    }
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Verbose",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

The application uses configuration to configure the right connection string. Access to the ConnectionString setting is achieved through this key: Data:DefaultConnection:ConnectionString.

The settings required by your application and the mechanism used to specify those settings (configuration being one example) can be decoupled using the options pattern. To use the options pattern you create your own settings class (probably several different classes, corresponding to different cohesive groups of settings) that you can inject into your application using an options service. You can then specify your settings using configuration or whatever mechanism you choose.

Note

You could store your Configuration instance as a service, but this would unnecessarily couple your application to a single configuration system and specific configuration keys. Instead, you can use the Options pattern to avoid these issues.

Using the built-in providers

The configuration framework has built-in support for JSON, XML, and INI configuration files, as well as support for in-memory configuration (directly setting values in code) and the ability to pull configuration from environment variables and command line parameters. Developers are not limited to using a single configuration provider. In fact several may be set up together such that a default configuration is overridden by settings from another provider if they are present.

Adding support for additional configuration file providers is accomplished through extension methods. These methods can be called on a ConfigurationBuilder instance in a standalone fashion, or chained together as a fluent API, as shown.

1
2
3
4
var config = builder.Build();

builder.AddEntityFramework(options => options.UseSqlServer(config["Data:DefaultConnection:ConnectionString"]));
config = builder.Build();

The order in which configuration providers are specified is important, as this establishes the precedence with which settings will be applied if they exist in multiple locations. In the example above, if the same setting exists in both appsettings.json and in an environment variable, the setting from the environment variable will be the one that is used. The last configuration provider specified “wins” if a setting exists in more than one location. The ASP.NET team recommends specifying environment variables last, so that the local environment can override anything set in deployed configuration files.

Note

To override nested keys through environment variables in shells that don’t support : in variable names replace them with __ (double underscore).

It can be useful to have environment-specific configuration files. This can be achieved using the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public Startup(IHostingEnvironment env)
{
    // Set up configuration providers.
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

    if (env.IsDevelopment())
    {
        // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
        builder.AddUserSecrets();
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

The IHostingEnvironment service is used to get the current environment. In the Development environment, the highlighted line of code above would look for a file named appsettings.Development.json and use its values, overriding any other values, if it’s present. Learn more about Working with Multiple Environments.

Warning

You should never store passwords or other sensitive data in provider code or in plain text configuration files. You also shouldn’t use production secrets in your development or test environments. Instead, such secrets should be specified outside the project tree, so they cannot be accidentally committed into the provider repository. Learn more about Working with Multiple Environments and managing Safe Storage of Application Secrets.

One way to leverage the order precedence of Configuration is to specify default values, which can be overridden. In this simple console application, a default value for the username setting is specified in a MemoryConfigurationProvider, but this is overridden if a command line argument for username is passed to the application. You can see in the output how many configuration providers are configured at each stage of the program.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;

namespace ConfigConsole
{
    public class Program
    {
        public void Main(string[] args)
        {
            var builder = new ConfigurationBuilder();
            Console.WriteLine("Initial Config Providers: " + builder.Providers.Count());

            var defaultSettings = new MemoryConfigurationProvider();
            defaultSettings.Set("username", "Guest");
            builder.Add(defaultSettings);
            Console.WriteLine("Added Memory Provider. Providers: " + builder.Providers.Count());

            builder.AddCommandLine(args);
            Console.WriteLine("Added Command Line Provider. Providers: " + builder.Providers.Count());

            var config = builder.Build();
            string username = config["username"];

            Console.WriteLine($"Hello, {username}!");
        }
    }
}

When run, the program will display the default value unless a command line parameter overrides it.

../_images/config-console.png

Using Options and configuration objects

Using Options you can easily convert any class (or POCO - Plain Old CLR Object) into a settings class. It’s recommended that you create well-factored settings objects that correspond to certain features within your application, thus following the Interface Segregation Principle (ISP) (classes depend only on the configuration settings they use) as well as Separation of Concerns (settings for disparate parts of your app are managed separately, and thus are less likely to negatively impact one another).

A simple MyOptions class is shown here:

1
2
3
4
5
public class MyOptions
{
    public string Option1 { get; set; }
    public int Option2 { get; set; }
}

Options can be injected into your application using the IOptions<TOptions> service. For example, the following MVC controller uses IOptions<MyOptions> to access the settings it needs to render the Index view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class HomeController : Controller
{
    public HomeController(IOptions<MyOptions> optionsAccessor)
    {
        Options = optionsAccessor.Value;
    }

    MyOptions Options { get; }

    // GET: /<controller>/
    public IActionResult Index()
    {
        return View(Options);
    }
}

Learn more about Dependency Injection.

To setup the IOptions<TOption> service you call the AddOptions() extension method during startup in your ConfigureServices method:

1
2
3
4
5
public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();
}

The Index view displays the configured options:

../_images/index-view.png

You configure options using the Configure<TOption> extension method. You can configure options using a delegate or by binding your options to configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();

    // Configure MyOptions using config
    services.Configure<MyOptions>(Configuration);

    // Configure MyOptions using code
    services.Configure<MyOptions>(myOptions =>
    {
        myOptions.Option1 = "value1_from_action";
    });

    // Add framework services.
    services.AddMvc();

When you bind options to configuration each property in your options type is bound to a configuration key of the form property:subproperty:.... For example, the MyOptions.Option1 property is bound to the key Option1, which is read from the option1 property in appsettings.json. Note that configuration keys are case insensitive.

Each call to Configure<TOption> adds an IConfigureOptions<TOption> service to the service container that is used by the IOptions<TOption> service to provide the configured options to the application or framework. If you want to configure your options some other way (ex. reading settings from a data base) you can use the ConfigureOptions<TOptions> extension method to you specify a custom IConfigureOptions<TOption> service directly.

You can have multiple IConfigureOptions<TOption> services for the same option type and they are all applied in order. In the example above value of Option1 and Option2 are both specified in appsettings.json, but the value of Option1 is overridden by the configured delegate.

Writing custom providers

In addition to using the built-in configuration providers, you can also write your own. To do so, you simply inherit from ConfigurationProvider, and populate the Data property with the settings from your configuration provider.

Example: Entity Framework Settings

You may wish to store some of your application’s settings in a database, and access them using Entity Framework (EF). There are many ways in which you could choose to store such values, ranging from a simple table with a column for the setting name and another column for the setting value, to having separate columns for each setting value. In this example, I’m going to create a simple configuration provider that reads name-value pairs from a database using EF.

To start off we’ll define a simple ConfigurationValue entity for storing configuration values in the database:

1
2
3
4
5
public class ConfigurationValue
{
    public string Id { get; set; }
    public string Value { get; set; }
}

We also need a ConfigurationContext to store and access the configured values using EF:

1
2
3
4
5
6
7
8
9
public class ConfigurationContext : DbContext
{
    public ConfigurationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<ConfigurationValue> Values { get; set; }

}

Next, create the custom configuration provider by inheriting from ConfigurationProvider. The configuration data is loaded by overriding the Load method, which reads in all of the configuration data from the configured database. For demonstration purposes, the configuration provider also takes care of initializing the database if it hasn’t already been created and populated:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class EntityFrameworkConfigurationProvider : ConfigurationProvider
{
    public EntityFrameworkConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<ConfigurationContext>();
        OptionsAction(builder);

        using (var dbContext = new ConfigurationContext(builder.Options))
        {
            dbContext.Database.EnsureCreated();
            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private IDictionary<string, string> CreateAndSaveDefaultValues(ConfigurationContext dbContext)
    {
        var configValues = new Dictionary<string, string>
            {
                { "key1", "value_from_ef_1" },
                { "key2", "value_from_ef_2" }
            };
        dbContext.Values.AddRange(configValues
            .Select(kvp => new ConfigurationValue() { Id = kvp.Key, Value = kvp.Value })
            .ToArray());
        dbContext.SaveChanges();
        return configValues;
    }
}

By convention we also add an AddEntityFramework extension method for adding the configuration provider:

1
2
3
4
5
6
7
public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEntityFramework(this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
    {
        return builder.Add(new EntityFrameworkConfigurationProvider(setup));
    }
}

You can see an example of how to use this custom ConfigurationProvider in your application in the following example. Create a new ConfigurationBuilder to setup your configuration providers. To add the EntityFrameworkConfigurationProvider you first need to specify the data provider and connection string. How should you configure the connection string? Using configuration of course! Add an appsettings.json file as a configuration provider to bootstrap setting up the EntityFrameworkConfigurationProvider. By reusing the same ConfigurationBuilder any settings specified in the database will override settings specified in appsettings.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Program
{
    public static void Main(string[] args)
    {
        var builder = new ConfigurationBuilder();
        builder.AddJsonFile("appsettings.json");
        builder.AddEnvironmentVariables();
        var config = builder.Build();

        builder.AddEntityFramework(options => options.UseSqlServer(config["Data:DefaultConnection:ConnectionString"]));
        config = builder.Build();

        Console.WriteLine("key1={0}", config["key1"]);
        Console.WriteLine("key2={0}", config["key2"]);
        Console.WriteLine("key3={0}", config["key3"]);

    }
}

Run the application to see the configured values:

../_images/custom-config.png

Summary

ASP.NET 5 provides a very flexible configuration model that supports a number of different file-based options, as well as command-line, in-memory, and environment variables. It works seamlessly with the options model so that you can inject strongly typed settings into your application or framework. You can create your own custom configuration providers as well, which can work with or replace the built-in providers, allowing for extreme flexibility.