December 2017

Volume 32 Number 12

[Cutting Edge]

Configuring ASP.NET Core Applications

By Dino Esposito | December 2017

Dino EspositoThe logic of any realistic software application depends on some external configuration data that, when fetched, drives the overall behavior of the application. Generally speaking, there are three types of configuration data: data fetched once and used everywhere, data fetched frequently and used everywhere, and data fetched on-demand just before use. The implementation of the latter two types of configuration data are, for the most part, application-­specific. The first type, data fetched only once in the application’s lifetime, tends to address the building of a wrapper API that hides the actual data store as much as possible.

In classic ASP.NET, the web.config file was, among many other things, the favorite repository of application-wide configuration data. In ASP.NET Core, there’s no web.config file anymore, yet the configuration API is richer and more flexible than ever before.

Configuration Data Providers

The configuration API is centered on the concept of the data provider. The data provider retrieves data from a given source and exposes it to the application in the form of name/value pairs. ASP.NET Core comes with a few predefined configuration providers able to read from text files (most notably JSON files), environment variables and in-memory dictionaries. Configuration providers are plugged into the system at application startup through the services of the ConfigurationBuilder class. All providers linked to the builder contribute their own name/value pairs and the resulting collection is exposed as an IConfigurationRoot object.

While data always comes in as a name/value pair, the overall structure of the resulting configuration object might be hierarchical, as well. It all depends on the actual nature and structure of the value associated with the name. Taken from the constructor of a startup class, the following code snippet shows how to build a configuration tree combining together two different configuration providers:

// Env is a reference to the IHostingEnvironment instance
// that you might want to inject in the class via ctor
var dom = new ConfigurationBuilder()
  .SetBasePath(env.ContentRootPath)
  .AddJsonFile("MyAppSettings.json")
  .AddInMemoryCollection(new Dictionary<string, string> {{"Timezone", "+1"}})
  .Build();

The AddJsonFile extension method adds name/value pairs from the properties stored in the specified JSON file. Note that the JSON file is listed through a relative path. The SetBasePath method, in fact, sets the root directory where the system will start looking for any such referenced files. Any JSON files can be used as a configuration data source. The structure of the files is completely up to you and can include any level of nesting. Multiple JSON files can be linked at the same time.

The AddInMemoryCollection method adds the content of the specified dictionary. Note that both the key and the value type of the dictionary must be string. At first sight, such an extension method might seem of little help, because it just adds static data that can only be set at compile time. However, an in-memory collection still allows you to isolate parametric data and the provider model of the ASP.NET Core configuration API decouples that data from the main body of the application and injects it for you at startup, like so:

new ConfigurationBuilder()
  .AddInMemoryCollection(
    new Dictionary<string, string> {{"Timezone", "+1"}})
  .Build();

In the previous code snippet, for example, a value representing the timezone to use is appended to the configuration builder, and the rest of the application receives it through the unified interface of the configuration API. In this way, you don’t have to change anything other than the provider and the actual storage pattern to read the timezone (as well as any other data you inject from memory) from other data sources.

Finally, the method AddEnvironmentVariables adds any environment variables defined in the server instance to the configuration tree. Note that all defined environment variables are added as a single block to the tree. If you need filtering, you’re best off opting for an in-memory provider and copying only selected variables to the dictionary.

Note that in ASP.NET Core 2.0 you can also inject IConfiguration right in the constructor of the startup class, and have the configuration tree automatically configured with environment variables and content from two JSON files: appsettings.json and appsettings.development.json. If you want JSON files with a different name or another list of providers, just build the configuration tree from scratch as follows:

var dom = new ConfigurationBuilder()
  .SetBasePath(env.ContentRootPath)
  .AddJsonFile("MyAppSettings.json")
  .AddInMemoryCollection(new Dictionary<string, 
    string> {{"Timezone", "+1"}})
  .Build();

Custom Configuration Providers

In addition to using predefined providers, you can also create your own configuration provider. To build a custom configuration provider, you start with a wrapper configuration source class—a plain class that implements IConfigurationSource. Here’s an example:

public class MyDatabaseConfigSource : IConfigurationSource
{
  public IConfigurationProvider Build(IConfigurationBuilder builder)
  {
    return new MyDatabaseConfigProvider();
  }
}

In the implementation of the method Build, as shown in the previous code snippet, you finally reference the actual provider—namely a class that inherits from the system-defined Configuration­Provider class (see Figure 1).

Figure 1 Sample Database-Driven Configuration Provider

public class MyDatabaseConfigProvider : ConfigurationProvider
{
  private const string ConnectionString = "...";
  public override void Load()
  {
    using (var db = new MyDatabaseContext(ConnectionString))
    {
      db.Database.EnsureCreated();
      Data = !db.Values.Any()
        ? GetDefaultValues(db)
        : db.Values.ToDictionary(c => c.Id, c => c.Value);
    }
  }
  private IDictionary<string, string> GetDefaultValues (MyDatabaseContext db)
  {
    // Pseudo code for determining default values to use
    var values = DetermineDefaultValues();
    // Save default values to the store
    // NOTE: Values is a DbSet<T> in the DbContext being used
    db.Values.AddRange(values);
    db.SaveChanges();
    // Return configuration values
    return values;
  }
}

A common example is a configuration provider that reads from an ad hoc database table. The provider may ultimately hide the schema of the table and the layout of the database involved. The connection string, for example, might be a private constant value. Such a configuration provider would likely use Entity Framework (EF) Core to perform data access tasks and, therefore, needs to have available a dedicated DbContext class and a dedicated set of entity classes to fetch values to be later converted into a string/string dictionary. As a nice touch, you might want to define default values for any of the values expected to be found and populate the database, if empty.

The database-driven provider discussed here is closed around a well-known database table. However, if you find a way to pass a DbContextOptions object as an argument to the provider, you can even manage to work with a rather generic EF-based provider. An example of this technique can be found at bit.ly/2uQBJmK.

Building the Configuration Tree

The resulting configuration tree—personally, I like to call it the configuration document object model—is commonly built in the constructor of the startup class. The output generated by the Build method of the ConfigurationBuilder class is an object of type IConfigurationRoot. The startup class will provide a member to save the instance for further use at a later time throughout the entire application stack, like this:

public class Startup
{
  public IConfigurationRoot Configuration { get; }
  public Startup(IHostingEnvironment env)
  {
    var dom = new ConfigurationBuilder()
      .SetBasePath(env.ContentRootPath)
      .AddJsonFile("MyAppSettings.json")
      .Build();
     Configuration = dom;
  }
}

The IConfigurationRoot object is the connection point for the application components to read the individual values in the configuration tree.

Reading Configuration Data

To read configuration data programmatically, you use the GetSection method on the IConfigurationRoot object. The value is read as a plain string. To identify the exact value you want to read, you provide a path string in which the colon symbol (:) is used to delimit properties in a hierarchical schema. Suppose that your ASP.NET Core project includes a JSON file like the one in Figure 2.

Figure 2 A Sample JSON File

{
  "ApplicationTitle" : "Programming ASP.NET Core",
  "GeneralSettings" : {
    "CopyrightYears" : [2017, 2018],
    "Paging" : {
      "PageSize" : 20,
      "FreezeHeaders" : true
    },
    "Sorting" : {
      "Enabled" : true
    }
  }
}

To read settings, you can proceed in many different ways, but it’s mandatory that you know how to navigate to the actual value in the configuration tree. For example, to locate the place where the default size of the page is stored the path is:

Generalsettings:Paging:PageSize

Note that a path string is always case-insensitive. In light of this, the simplest way to read settings is through the indexer API, like this:

var pageSize = Configuration["generalsettings:paging:pagesize"];

It’s important to note that the indexer API returns the value of the setting as a string, but you can also use an alternate strongly typed API. Here’s how that approach looks:

var pathString = "generalsettings:paging:pagesize";
var pageSize = Configuration.GetValue<int>(pathString);

The reading API is independent of the actual data source. You use the same API to read hierarchical content from JSON as you do to read flat content from in-memory dictionaries.

In addition to direct reading, you can leverage a positioning API, which will conceptually move the reading cursor on a specific configuration subtree. The GetSection method lets you select an entire configuration subtree where you can act on using both the indexer and the strongly typed API. The GetSection method is a generic query tool for the configuration tree and isn’t specific of JSON files only. An example is shown here:

var pageSize = Configuration.GetSection("Paging").GetValue<int>("PageSize");

Note that for reading you also have available a GetValue method and the Value property. Both would return the value of the setting as a plain string.

Refreshing Loaded Configuration

In ASP.NET Core, the configuration API is designed to be read-only. This only means that you can’t write back to the configured data source using an official API. If you have a way to edit the content of the data source (that is, programmatic overwrites of text files, database updates and the like), then the system allows you reload the configuration tree programmatically.

To reload a configuration tree, all you need to do is call the Reload method in the configuration root object.

Configuration.Reload();

Typically, you might want to use this code from within an admin page where you offer users a form to update stored settings. As far as JSON files are concerned, you can also enable automatic reloads of the content upon changes. You just add an extra parameter to AddJsonFile, like so:

var dom = new ConfigurationBuilder()
  .AddJsonFile("MyAppSettings.json", optional: true, reloadOnChange: true);

JSON files are the most popular of the text file formats natively supported by ASP.NET Core. You can also load settings from XML and .ini files. You just add a call to AddXmlFile and AddIniFile methods with the same rich signature of AddJsonFile.

Note that in ASP.NET Core 2, configuration can also be managed right from the program.cs file, as shown here:

return new WebHostBuilder()
  .UseKestrel()
  .UseContentRoot(Directory.GetCurrentDirectory())
  .ConfigureAppConfiguration((builderContext, config) =>
  {
    var env = builderContext.HostingEnvironment;
    config.AddJsonFile("appsettings.json")
  });

If you do, then you can inject the configuration tree in the startup class via IConfiguration in the constructor.

Passing Configuration Data Around

The configuration root object lives within the scope of the startup class, but its content should be made available throughout the entire application. The natural way of achieving this goal in ASP.NET Core is via dependency injection (DI). To share the configuration root object with the system, you just bind it to the DI system as a singleton.

public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton(Configuration);
  ...
}

After that, you can inject an IConfigurationRoot reference in any controller constructors and Razor views. Here’s an example for a view:

@inject IConfigurationRoot Configuration
CURRENT PAGE SIZE IS @Configuration["GeneralSettings:Paging:PageSize"]

While injecting the configuration root object is possible, and to a large extent even easy, it still results in a fairly cumbersome API if access to configuration settings occurs too often. That’s why the ASP.NET Core configuration API offers a serialization mechanism that lets you save the configuration tree, all or in part, to a separate POCO class.

Serializing to POCO Classes

If you’ve ever done any ASP.NET MVC programming, you should be familiar with the concept of model binding. A similar pattern—called the Options pattern—can be used in ASP.NET Core to do this, as shown here:

public void ConfigureServices(IServiceCollection services)
{
  // Initializes the Options subsystem
  services.AddOptions();
  // Maps the PAGING section to a distinct dedicated POCO class
  services.Configure<PagingOptions>(
    Configuration.GetSection("generalsettings:paging"));
}

In this example, you first initialize the configuration binding layer, as shown in the previous code snippet, and then explicitly ask to try to bind the content of the specified subtree to the public properties of the given class, like this:

public class PagingOptions
{
  public int PageSize { get; set; }
  public bool FreezeHeaders { get; set; }
}

The bound class must be a POCO class with public getter/setter properties and a default constructor. The Configure method will attempt to fetch and copy values from the specified section of the configuration tree right into a freshly created instance of the class. Needless to say, binding will silently fail if values aren’t convertible to declared types.

Such a POCO class can be passed around throughout the appli­cation stack using the built-in DI system. You don’t have to do anything for this to happen. Or rather, any configuration required is applied when you invoke AddOptions. Just one step remains to programmatically access the configuration data serialized to a class:

public PagingOptions PagingOptions { get; }
public CustomerController(IOptions<PagingOptions> config)
{
  PagingOptions = config.Value;
}

If you use the Options pattern extensively in all of your controllers, you might consider moving the options property (that is, Paging­Options) to a base class and then inherit your controller classes from there. Likewise, you can inject IOptions<T> in any Razor views.

Wrapping Up

In classic ASP.NET MVC, the best practice for dealing with configuration data mandates that you load all of your data once at startup into a global container object. The global object is then accessible from controller methods and its content can be injected as an argument into back-end classes such as repositories and even into views. In classic ASP.NET MVC, the cost of mapping loose string-based data into the strongly typed properties of the global container is entirely on you.

In ASP.NET Core, you have both a low-level API to read individual values in a much more precise way than the ConfigurationManager API of old ASP.NET, and an automatic way to serialize external content into POCO classes of your design. This is possible because the introduction of the configuration tree—populated by a list of providers—decouples configuration from the main body of the application.


Dino Esposito is the author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) and “Programming ASP.NET Core” (Microsoft Press, 2018). Pluralsight author and developer advocate at JetBrains, Esposito shares his vision of software on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Ugo Lattanzi
Ugo is a programmer specializing in enterprise application development using different tools and languages, with a focus on Web applications, service-oriented applications and  environments where scalability is a top priority. Ugo has been a Microsoft MVP for the past nine years.


Discuss this article in the MSDN Magazine forum