ASP.NET 5 Configuration

One of the many changes introduced by ASP.NET 5 is the new configuration framework. Actually, the new configuration packages are not specific to ASP.NET; they are a generic way of accessing key/value settings which can be loaded from different sources (e.g. JSON files, command-line arguments).

The configuration topic is not very broad and a lot has already been written, namely this blog post and the official ASP.NET documentation. As a recap, here’s an example of how to create a Configuration instance from JSON files and environment variables. Note that order is relevant (values from later sources win).

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IHostingEnvironment env, IApplicationEnvironment app)
    {
        var configuration = new ConfigurationBuilder(app.ApplicationBasePath)
            .AddJsonFile("config.json")
            .AddJsonFile($"config.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

        Configuration = configuration.Build();
    }

    public void Configure(IApplicationBuilder app)
    {
        // Use Configuration here
    }
}

As a side note, the configuration packages suffered some reorganizations on beta 5. The example above is based on beta 7.

The infamous Web.config files are (practically) gone and the different middlewares aren’t implicitly getting their configurations from some configuration section. In fact, as far as I can tell, configurations are explicit via code on the application’s Startup.Configure method. The simple configuration framework can help you do the rest.

One common configuration scenario is having a few related application settings and wanting to get them inject into, lets say, a controller. When using Web.config files one probably would use the System.Configuration classes and define a type for a custom configuration section. The thing is: using System.Configuration is soooo painful. Of course one could use an add-in for VS or other simpler approaches, but it’s always bothered me that there wasn’t a straightforward way of having simple application settings bound to a dedicated configuration section out of the box.

One of the best things that comes with the new configuration system is the Options package. It builds on top of the configuration packages and adds support for easily representing settings using POCOs and have them registered on the DI infrastructure. Here’s an example.

Let’s say the JSON config file registered on the previous example has the following contents:

{
  "AppSettings": {
    "SiteTitle": "Awesome site"
  }
}

We can define a POCO to represent “AppSettings”:

public class AppSettings
{
    public string SiteTitle { get; set; }
}

And then, on our Startup class we just need to add the Options services and use the Configure extension method, which is part of the options package:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions(); // AddMvc() actually adds this internally
    services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
    // ...
}

This binds the settings to an instance of AppSettings and registers it on the DI infrastructure. To access the settings from a controller (or any other class) the IOptions interface is used, as illustrated below.

public class HomeController
{
    private readonly AppSettings settings;

    public HomeController(IOptions<AppSettings> settings)
    {
        this.settings = settings.Options;
    }

    // ...
}

Pretty straightforward, right? As usual, I’ll go into some details on how the options classes are registered by the Configure extension method above.

Starting on the Configure method itself, we can see that it creates an instance of ConfigureFromConfigurationOptions with the given Configuration and registers it.

public static IServiceCollection Configure<TOptions>(
    this IServiceCollection services,
    IConfiguration config)
{
    services.ConfigureOptions(new ConfigureFromConfigurationOptions<TOptions>(config));
    return services;
}
public static IServiceCollection ConfigureOptions(
    this IServiceCollection services,
    object configureInstance)
{
    var serviceTypes = FindIConfigureOptions(configureInstance.GetType());
    foreach (var serviceType in serviceTypes)
    {
        services.AddInstance(serviceType, configureInstance);
    }
    return services;
}

The ConfigureOptions method above seems more interesting. It does the following:

  1. Find concretizations of IConfigureOptions<T> on the given instance’s type. In our case, we saw that an instance of ConfigureFromConfigurationOptions is used. If we follow the type’s inheritance hierarchy, we can see that it implements IConfigureOptions for the given options type (AppSettings, in our case).
  2. Registers all the implemented configure options interfaces on the DI infrastructure, having them resolved to the given instance.

The IConfigureOptions<TOptions> interface is very simple:

public interface IConfigureOptions<in TOptions>
{
    void Configure(TOptions options);
}

Note that it gets an instance of the options and should mutate it as needed. If you’re wondering why, I’ll get into that in a moment.

So, at this point the DI container knows that our ConfigureFromConfigurationOptions<AppSettings> should be used to configure AppSettings from the given Configuration section. It is a good time to point that this class uses a ConfigurationBinder to bind the configuration keys/values to the options properties.

configure_options

As we previously discussed, IOptions<T> is used to access the option instances. How do we get there? The answer lies on the AddOptions method, used to enable the options framework:

public static IServiceCollection AddOptions(IServiceCollection services)
{
    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
    return services;
}

Ok, so this binds (on DI) all the concretizations of IOptions<T> to the corresponding OptionsManager<T>, which is the bridge between IConfigureOptions and IOptions:

public class OptionsManager<TOptions> : IOptions<TOptions>
{
  public OptionsManager(IEnumerable<IConfigureOptions<TOptions>> setups)
  {
    _setups = setups;
  }

  // ...
}

This class gets a set of IConfigureOptions for its options type (resolved by DI), meaning that you can call the Configure method multiple times, hence registering multiple IConfigureOptions on DI. This is why the IConfigureOptions interface mutates an existing instance: it may be working over an object that already has some (or all) of its properties populated. This allows overriding/combining values from different actions/sources, just like it is done on the Configuration classes.

The configuration logic just described is implemented on the Options property of the manager, as illustrated below. Note that the configuration is executed only once. In addition, the OptionsManager was registered as a singleton on DI, which means there’s a single IOptions instance for each options type in the application.

public virtual TOptions Value
{
    get
    {
        if (_options == null)
        {
            _options = _setups == null
                ? new TOptions()
                : _setups.Aggregate(new TOptions(),
                                    (options, setup) =>
                                    {
                                        setup.Configure(options);
                                        return options;
                                    });
        }
        return _options;
    }
}

Here’s a more complete diagram of the types in use:

options

I’d prefer an approach where the controller dependency was directly AppSettings. At a glace, I think it would have been possible to avoid IOptions<T> but, well, it works this way too.

Advertisements