Applications often demand configuration values, those can either be connection strings, logging level settings or specific tokens, for example. While developing with .NET Core framework the configuration mechanism is based at Microsoft.Extensions.Configuration. This is a replacement for System.Configuration
namespace. In this post, I will try to explain how to use the first effectively.
Host
This journey starts with a configuration and launch of a host. The host is responsible for the application startup, the request processing pipeline and provides extensibility to manage configurations. In the case of ASP.NET Core the host is based on IWebHostBuilder
.
IWebHostBuilder
Since ASP.NET Core 2.0 the host is created with pre-configured defaults at the application entry point. Program.cs
contains a WebHost.CreateDefaultBuilder
method that automatically produces a IWebHostBuilder
instance as you will notice here. CreateDefaultBuilder
executes other tasks among which are, use Kestrel as the web server, register the configuration sources for file providers (JSON) with multiple environment support, register user secrets for the current assembly, register environment variables and command-line arguments if not null or even logging configuration.
The configurations can be overridden through ConfigureAppConfiguration
, see an example below.
Configuration providers
There is flexibility to choose several types of default configuration providers, such as:
- File configuration (JSON, XML, INI)
- Environment variables
- Command-line (command-line arguments)
- Memory (In-memory collections)
- Azure Key Vault
You can create your own custom configuration providers using the interfaces IConfigurationSource
and IConfigurationProvider
.
Specific environments
Multiple files to handle specific environment configurations can be easily added. In my opinion, this enables a clean and better organized configuration, you can add configuration files for specific environments of the application deployment lifecycle in your root folder.
This is possible because of the following. The environment is read from the ASPNETCORE_ENVIRONMENT variable that is set in the launchSettings.json
file.
Overriding values
In the screenshot there is a file with no environment value: appsettings.json
. This file should hold default values that do not change based on environment. Other values present in the environment specific JSON files are overridden while the application starts.
It’s important to mention that despite the chosen provider all your configuration sources will come down to flatten key/value pairs.
Reading values
Currently the appsettings.Development.json
contains the following section.
You should never store sensitive information in your configuration files. Take advantage of user secrets, for example.
Read a value in HomeController
can be achieved by using IConfiguration
interface.
The example displays the extension method GetValue<T>
extracting the value with the specific key from the JSON file. One could also simply access the property directly.
Make notice that the keys are case-insensitive and follow the hierarchy specified on the JSON file using delimiter character ”:”. See documentation of IConfiguration
.
Reading values with strongly typed objects
I’ve created a strongly typed configuration class: MySettings.cs
with a structure that matches the same section in the JSON configuration file displayed previously.
Using MySettings
is possible with the extension method Bind()
.
Quite often used is the Options
pattern. This leverages strongly typed objects to represent a group of related settings described in the used sources.
The Startup
class contains a ConfigureServices
method where you should register your desired configuration. This is provided through the method services.Configure<T>
.
Read a value in HomeController
can be achieved by using IOptions<T>
in the constructor, in this example T
of type MySettings
.
Say no to IOptions dependencies
One could say that the classes that access your configurations should not be dependent on IOptions<T>
, but instead on your configuration classes itself. To achieve this explicitly register MySettings
object on the ConfigureServices
method as a singleton.
Read the value in HomeController
does not dependend on IOptions<T>
anymore.
Flexibility is a key element regarding the configuration mechanism in .NET Core.
Validation
There are a few recurrent topics I’ve found in projects that should require extra attention. A binding of a property fails because of a typo in your configuration source or class when using strongly typed objects. A value is removed from the configuration sources, are just some to take in consideration. I want to accomplish a versatile direction to validate settings.
The interface ISartupFilter
allows you to configure middleware pipeline from a service resolved from the dependency injection container. ISartupFilter
exists in the Microsoft.AspNetCore.Hosting.Abstractions
package. Read more detailed information in the documentation.
I’ve created the following startup filter, relying on a collection of IValidatable
settings. This allows validation of configuration objects during the initialization of the application. When the result of the validation operation is not true an invalid operation exception is logged with the setting name, validation results and thrown.
The IValidatable
interface returns a boolean representing the success or not of the operation and a collection of validation results.
To keep the validation extensible and because we can validate our settings in many different ways, I’ve created an abstract class depending on DataAnnotations that inherits the IValidatable
interface.
MySettings
should inherit from DataAnnotationSettingValidation
. In this example I’ve used a required attribute in a property.
Last but not least, there is a need to register the startup filter and the desired settings as IValidatable
in the Startup
class on the ConfigureServices
method.
TLDR
Microsoft.Extensions.Configuration is open-source, flexible and easy to use. In my opinion, definitely a step forward compared to System.Configuration
.
There are many available default providers and ways to read configuration data in your applications. I favor usage of strongly typed objects with Options
pattern. In the presented examples, all your configuration objects can be validated during the startup of the application, preventing it to load with wrong values or errors.
Make sure you do not store sensitive information or passwords in your configuration files.
I’ve wrote a sample application to support this post. Furthermore there is great documentation provided by Microsoft.