In part 1 of this article series Dependency Injection was covered – one of the features of the Host class. This article shows another aspect that’s needed by nearly every application: configuration. Here, I’m covering using the ConfigurationBuilder
, using the IConfiguration
API, injecting this interface, and what’s in the Host
class.
Overview
With the old .NET Framework, in Web applications, configuration values are stored in the XML file web.config. This file not only contains application settings, but a lot more such as redirects of assemblies based on versions, and runtime configuration. To allow having different configuration values for staging and production servers, and not the need to create copies of complete files (and to miss some changes in future versions), XML transformations can be done – and are supported with Visual Studio.
.NET in its actual version offers more flexibility, it’s easier to use, and it’s more powerful with configurations. You can use different providers to store application settings in JSON files, environmental variables, and command line arguments, and easily add other providers such as having the configuration stored with Azure App Configuration or Azure Key Vault.
Let’s get into an example.
Using Simple Configuration
The first code snippet is part of a .NET Core console application, and the configuration setup defines to read configuration from the JSON file appsettings.json. The NuGet package Microsoft.Extensions.Configuration is needed for all the configuration types, such as the ConfigurationBuilder. To read the configuration file from JSON, another NuGet package, Microsoft.Extensions.Configuration.Json is required. Creating a new ConfigurationBuilder
instance, a Fluent API is offered. The SetBasePath
method defines the directory where the configuration files are read from there on. The AddJsonFile
extension method that has been made available from the Microsoft.Extensions.Configuration.Json package defines the filename for the configuration file. SetBasePath
needs to be invoked before the invocation of the AddJsonFile
. In case you want to read values from configuration files in different folders, SetBasePath
needs to be invoked multiple times – always before defining the file itself. After this filename is configured, the Build
method is invoked which returns an IConfigurationRoot
object. This returned object is returned from the SetupSimpleConfiguration
method.
private static IConfiguration SetupSimpleConfiguration()
=> new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
> The Build
method that is defined by the IConfigurationBuilder
interface returns an IConfigurationRoot
, wheras the method SetupSimpleConfiguration
returns IConfiguration
. IConfigurationRoot
derives from IConfiguration
and adds the Providers
property and the Reload
method. The Providers
property returns a list of configuration configuration providers. The Reload
method can be invoked to refresh configuration values when they probably have been changed in the file system while the application was running.
The configuration file applicationsettings.json defines a simple configuration value for the key SimpleConfig:
{
"SimpleConfig": "SimpleValue"
}
To read the configuration value, all what’s needed is to use the indexer of the IConfiguration
interface and pass the name of the key:
private static void ReadSimpleConfiguration(IConfiguration configuration)
{
Console.WriteLine(nameof(ReadSimpleConfiguration));
string val1 = configuration["SimpleConfig"];
Console.WriteLine($"Read {val1} using the key SimpleConfig");
Console.WriteLine();
}
The variable val1 now contains the string SimpleValue and is shown on the console.
Using Different Configuration Providers
There’s no need to store your configuration values within JSON files. You can use XML files, also can make use of INI files, pass configuration files with command line arguments, or environmental variables – you can use any provider for configuration values, or create your own. To use configuration files from XML files, the NuGet package Microsoft.Extensions.Configuration.Xml can be used, with environmental variables the package Microsoft.Extensions.Configuration.EnvironmentVariables.
private static IConfiguration SetupConfigurationWithMultipleProviders(string[] args) =>
new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
To run the application from Visual Studio, the program arguments as well as the environmental variables can be set from the Debug settings of the project properties:
> Using the same configuration keys with multiple providers, the order how the providers are added to the ConfigurationBuilder becomes important. The provider that is added last wins.
Defining Different Values for Development, Staging, Production
With the new configuration it is easy to supply different configuration values for development, staging, and production environments. With the sample project I’ve configured the environment variable DOTNET_Environment to the name of the environment, e.g. Staging. This environmental variable is used to define the filename for the configuration. appsettings.json is used for the configuration that is not different with the servers, with the development server the additional configuration file appsettings.development.json is used, with the production server appsettings.production.json. By default, if the file does not exist, an exception is thrown. Passing true with the second argument of AddJsonFile
, it can be specified that the setting is optional.
private static IConfiguration SetupConfigurationWithOptionalSettings()
{
string environment = Environment.GetEnvironmentVariable("DOTNET_Environment");
string environment = Environment.GetEnvironmentVariable("DOTNET_Environment") ?? "Production";
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.Build();
}
Reading from Sections
With the previous sample, no hierarchy was used. With JSON files it is also possible to define hierarchical levels. For example, the following JSON file defines a section ConnectionStrings, and the connection named DefaultConnection within this section:
{
"ConnectionStrings": {
"DefaultConnection": "Connection string to the staging database"
}
}
To read the connection string, the GetSection
method can be used passing the section name. The GetSection method returns an IConfigurationSection
where in turn again the indexer can be used to retrieve the values within this section.
For easier use of connection strings, the extension method GetConnectionString
exists that does the same – retrieving a key from the section ConnectionStrings.
private static void ReadConfigurationWithOptionalSettings(IConfiguration configuration)
{
Console.WriteLine(nameof(ReadConfigurationWithOptionalSettings));
Console.WriteLine(configuration.GetSection("ConnectionStrings")["DefaultConnection"]);
Console.WriteLine(configuration.GetConnectionString("DefaultConnection"));
Console.WriteLine();
}
Using the Host class
Using the Host
class and invoking the method CreateDefaultBuilder
a lot of the configuration is already done. This method adds these configuration providers:
- JSON file appsettings.json
- JSON provider appsettings.{environment}.json
- Provider for environmental variables
- Provider for command line arguments (in case the command-line arguments are passed to the
CreateDefaultBuilder
method) - User secrets when user secrets are configured
> Storing secrets in a source code repository is not a good idea. Here, user secrets help. With user secrets, the configuration is stored in the user profile. Of course, this option is only available during development.
With the second sample application, the Controller
class is created which expects that the IConfiguration
interface is injected. The variable where the object implementing the interface is assigned to is used in the method ReadConfigurationValues
:
public class Controller
{
private readonly IConfiguration _configuration;
public Controller(IConfiguration configuration) =>
_configuration = configuration;
public void ReadConfigurationValues()
{
var config1 = _configuration["Config1"];
Console.WriteLine($"config1: {config1}");
var connectionString = _configuration.GetConnectionString("DefaultConnection");
Console.WriteLine(connectionString);
}
}
To setup the Host
class, the static method CreateDefaultBuilder
is invoked. This method returns an IHostBuilder
. This IHostBuilder
is used to configure the dependency injection container (DI) calling the ConfigureServices
method. The Controller
class is registered, so that the container can inject the IConfiguration
interface. The IConfiguration
interface is one of the services registered with the DI containers method CreateDefaultBuilder
. The Build
method returns the host implementing the IHost
interface. The host
variable is then used to access the Controller
, and invoke the ReadConfigurationValues
method to display the configuration values.
static void Main(string[] args)
{
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddTransient();
})
.Build();
var controller = host.Services.GetRequiredService();
controller.ReadConfigurationValues();
}
All what now needs to be done is to create the appsettings.json file and configure the values (or to use environmental variables or command line arguments), and read retrieve the values.
Strongly Typed Access
For strongly typed access, .NET classes can be specified to define what configuration values are needed, such as the MyConfiguration
class and the InnerConfiguration
class. MyConfiguration
defines properties for string and int values, as well as the contained Inner
property to access values specified by InnerConfiguration
:
public class InnerConfiguration
{
public string InnerText { get; set; } = string.Empty;
}
public class MyConfiguration
{
public string Text1 { get; set; } = string.Empty;
public int Number1 { get; set; }
public InnerConfiguration Inner { get; } = new InnerConfiguration();
}
The configuration values are defined with keys having the same name as the properties defined:
{
"Config2": "from appsettings.json",
"ConnectionStrings": {
"DefaultConnection": "Connection string to the default database"
},
"MyGroup1": {
"Text1": "value for text1",
"Number1": 42,
"Inner": {
"InnerText": "value for inner text"
}
}
}
Reading the configuration values, it’s possible to bind the retrieved values to this class. In the Controller
class, an instance of the MyConfiguration
class is created, and using the IConfiguration
interface, and the values are assigned to the properties:
public void StronglyTypedConfiguration()
{
var settings = new MyConfiguration();
_configuration.GetSection("MyGroup1").Bind(settings);
Console.WriteLine($"text: {settings.Text1}");
Console.WriteLine($"number: {settings.Number1}");
Console.WriteLine($"inner text: {settings.Inner.InnerText}");
}
Using Configuration with Options
In the previous blog article of this series I’ve shown how to use the IOptions
interface to pass initialization data to a service class injected by using the DI container. The same initialization can be used to pass configuration data.
public class ControllerWithOptions
{
private readonly IOptions _options;
public ControllerWithOptions(IOptions options) =>
_options = options;
public void StronglyTypedConfiguration()
{
Console.WriteLine($"text: {_options.Value.Text1}");
Console.WriteLine($"number: {_options.Value.Number1}");
Console.WriteLine($"inner text: {_options.Value.Inner.InnerText}");
}
}
Using a Configure
extension method of the IServiceCollection
interface, an object implementing the IConfiguration
interface can be passed. To make this easy to combine it with the type ControllerWithOptions
where the configuration data needs to be set, the extension method AddControllerWithOptions
is specified:
public static class ControllerWithOptionsExtensions
{
public static IServiceCollection AddControllerWithOptions(
this IServiceCollection services,
IConfiguration configuration)
{
services.Configure(configuration);
return services.AddTransient();
}
}
On configuring of the Host
class, the IConfiguration
interface can now be passed retrieving the section MyGroup1 where the configuration values are stored. Because in this case accessing configuration information is needed on configuration of the services, an overload of ConfigureServices
is used where the HostBuilderContext
is the first parameter. Using this parameter, the configuration already defined by the Host
class can be accessed using the Configuration
property. Using the configuration
variable, configuration is retrieved with the GetSection
method. This method returns IConfigurationSection
which itself derives from the base interface IConfiguration
, and thus can be passed to the AddControllerWithOptions
method.
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
var configuration = context.Configuration;
services.AddControllerWithOptions(configuration.GetSection("MyGroup1"));
})
.Build();
Customizing configuration with the Host class
To add other configuration providers to .NET applications, the only thing needed are to add a NuGet package for the configuration provider, and to configure the provider with the ConfigureAppConfiguration
method, as shown in Azure App Configuration: Configuration of .NET Applications where the Azure App Configuration provider is added.
Take away
Configuration with .NET is very flexible in that it supports different configuration providers – no matter if the configuration is coming from JSON files, XML or INI files, environmental variables or command line arguments. The Host
class defines preconfigured configuration. Built-in from the Host
class is support to inject IConfiguration
with services, so service implementations can directly access values coming from configuration. Configuration can also be passed to a service when configuring the Host
class injecting the IOptions
interface with the service, and accessing configuration data using an overload of the ConfigureServices
method accessing the HostBuilderContext
.
If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

ou can get the complete sample code. See the ConfigurationWithHost sample solution in the Dotnet folder.
Enjoy learning and programming!
Christian
More Information
It’s all in the Host Class – Dependency Injection with .NET
Azure App Configuration: Configuration of .NET Applications
Azure Functions with Dependeny Injection
HTTP Client Factory with .NET Core
Implementation of the CreateDefaultBuilder Method on GitHub
More information on C# and programming .NET Core applications is in my book Professional C# 7 and .NET Core 2.0, and in my workshops.
Formula 1 Steering Wheel ID 143423851 © Logan Chislett | Dreamstime.com
3 thoughts on “It’s all in the Host Class – Part 2: Configuration”