Configuration management is something we cannot do without. It is a must-have feature. In this blog post, I am going to discuss how to manage and access configuration in ASP.Net Core applications. I will use ASP.Net Core 3.1 for demonstrating the example here.
Configuration management in ASP.Net Core has improved significantly compared to how it was managed in the legacy .Net Framework.
Firstly, it is saved as JSON instead of XML compared to the legacy .Net Framework. This makes reading and managing the configuration file extremely simple and user friendly.
Creating a new ASP.Net Core Application
To start this example, first of all, I will create a new ASP.NET Core 3.1 Web API Application.
Firstly, to do that, I will open up Visual Studio 2019. Once Visual Studio opens up, I will select the menu File -> New -> Project. This will open the Create a new Project project popup window.
Secondly, in the Create a new Project popup window, I will select ASP.NET Core Web Application from the project template and click on the Next button.
Thirdly, on the next page, I will provide the name of the application as Config.Demo
and click on the Create button.
Finally, on the final page, I will select the API template option. And I will keep other values default (ASP.Net Core 3.1) and click on the Create button.
The configuration file
By default, the Visual Studio project template will create an appsettings.json
and appsettings.Development.json
file when a new project is created.
These are the files where we save configurations based on the environment, where appsettings.json
is the default configuration file. The environment name is usually available as an environment variable, with key “ASPNETCORE_ENVIRONMENT”.
CreateDefaultBuilder
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public static class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Inside of the Program
class, inside of the CreateHostBuilder
function, we make a call to Host.CreateDefaultBuilder
. If we peek into the definition of the CreateDefaultBuilder
you can see it does the following:
- Firstly, It sets the
ContentRootPath
of theIHostEnvironment
to the current directory - Secondly, It goes and figures out the
appsettings.json
configuration file - And then also it loads the
appsettings.{environment}.json
config - Thirdly, it goes and it loads environment variables
- Fourthly, it loads anything passed to the arguments
Accessing configurations from Appsettings
For reading configurations from appsettings.development.json
, firstly, let us add a couple of entries to the appsetting.development.json
.
Now, for the first entry, I will add a key Version
, which will have a decimal value associated with it.
Next, for the second entry, I will add a key Settings
, which will have a complex object associated with it.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Version": "1.0",
"Settings": {
"Database": "SQL"
}
}
Now we will go to the Startup
class, and try to access these two configuration items from the ConfigureServices
method and print to the console as output.
To do that, I will access these values inside of the ConfigureServices
method of the Startup
class. And there are two ways to access the values from the configuration file.
- Firstly using
Configuration.GetValue
- Secondly using the indexer on
Configuration
property
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var version = Configuration.GetValue<string>("Version");
var db = Configuration["Settings:Database"];
Console.WriteLine(version);
Console.WriteLine(db);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Now if I run the application, I will see the Version
and Database
are printed in the console.
And as you can see from the code, we can access multiple layers of configuration values by separating the keys with :
, for example, here we are using Settings:Database
to access the value of Database
which is at the next level of Settings
.
Accessing configurations from Controller
Now that we are able to access configuration inside of the Startup
class, we can add the configurations to the dependency injection container and access it from the controller.
To do that I will update the WeatherForecastController
class and inject string version
to the constructor. And I will update the implementation of the Get
method to return this version
.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Config.Demo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly string version;
public WeatherForecastController(
ILogger<WeatherForecastController> logger,
string version)
{
_logger = logger;
this.version = version;
}
[HttpGet]
public string Get()
{
return version;
}
}
}
Once the controller is ready, I will go to the Startup
class to add the Version
configuration to the dependency injection. To do that we can write the following code in the ConfigureServices
method:
services.AddSingleton(version);
This will add the Version
as a sting to the dependency injection container.
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var version = Configuration.GetValue<string>("Version");
var db = Configuration["Settings:Database"];
services.AddSingleton(version);
Console.WriteLine(version);
Console.WriteLine(db);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Now, if I run the application and access /weatherforecast, I will see that version 1.0 is returned to the output.
Access complex configuration object
Now let us add another configuration section in the asspettings.development.json
configuration file. And I will name this configuration as Custom
, and it will be a multilevel configuration item. And Custom
have ConfigValue
as child level configuration.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Version": "1.0",
"Settings": {
"Database": "SQL"
},
"Custom": {
"ConfigValue": "Custom configuration"
}
}
Once the configuration item is added, firstly, I will create a class called Custom
. And inside the class, I will have a single property ConfigValue
of type string.
namespace Config.Demo
{
public class Custom
{
public string ConfigValue { get; set; }
}
}
Secondly, I will go to the Startup
class and add this configuration value to the dependency injection container. To do that, I will use the Bind
function of the IConfigurationSection
instance, which is returned from the Configuration.GetSection
method.
var custom = new Custom(); Configuration.GetSection("Custom").Bind(custom);
Thirdly, I can add this Custom object to the dependency injection container using services.AddSingleton
method.
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var version = Configuration.GetValue<string>("Version");
var db = Configuration["Settings:Database"];
services.AddSingleton(version);
var custom = new Custom();
Configuration.GetSection("Custom").Bind(custom);
services.AddSingleton(custom);
Console.WriteLine(version);
Console.WriteLine(db);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Fourthly, I will go to the WeatherForecastController
class and inject the Custom
object to the controller. And then I will update the Get
method to return the value of ConfigValue
property of the Custom
class as output.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Config.Demo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly string version;
private readonly Custom custom;
public WeatherForecastController(
ILogger<WeatherForecastController> logger,
string version,
Custom custom)
{
_logger = logger;
this.version = version;
this.custom = custom;
}
[HttpGet]
public string Get()
{
return custom.ConfigValue;
}
}
}
Finally, if I run the application, I will see the value of ConfigValue
, which is “Custom configuration” returned from the /weatherforecast API.
The more simplified way to add configuration value to the dependency injection container is to use the Configure
method of the IServiceCollection
method, instead of using the Bind
feature. But in that case, the dependency injection container will inject a generic IOptions
object for the type.
To demonstrate that, firstly, I will update the implementation of the Startup
class’s ConfigureServices
method to use the following code to configure the Custom
configuration section to the dependency injection container.
services.Configure<Custom>(Configuration.GetSection("Custom"));
Updated code for the Startup
class is as follows:
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var version = Configuration.GetValue<string>("Version");
var db = Configuration["Settings:Database"];
services.AddSingleton(version);
services.Configure<Custom>(Configuration.GetSection("Custom"));
Console.WriteLine(version);
Console.WriteLine(db);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Secondly, I will change the implementation of the WeatherForecastController
class, to take IOptions
of Custom
instead of just Custom
class as a dependency.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Config.Demo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly string version;
private readonly Custom custom;
public WeatherForecastController(
ILogger<WeatherForecastController> logger,
string version,
IOptions<Custom> custom)
{
_logger = logger;
this.version = version;
this.custom = custom.Value;
}
[HttpGet]
public string Get()
{
return custom.ConfigValue;
}
}
}
Now, if I run the application, I will see the exact same output as before.
Accessing Environment variables
This is one feature that I absolutely love about .Net Core, is that we can access even environment variables through the same IConfiguration
object. Which is really helpful.
To demonstrate this, firstly, I will add a new environment variable named TEST_ENV in the environment variables, and I will set its value to “Test_Environment_Variable”.
Secondly, I will go to the Startup
class and access this environment variable using Configuration["TEST_ENV"]
and print it out to the console.
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var version = Configuration.GetValue<string>("Version");
var db = Configuration["Settings:Database"];
services.AddSingleton(version);
services.Configure<Custom>(Configuration.GetSection("Custom"));
Console.WriteLine(version);
Console.WriteLine(db);
Console.WriteLine(Configuration["TEST_ENV"]);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Now if I run the application, I can see the environment variable output in the console.
Accessing Custom Configuration File
We can also add custom JSON configuration files to our projects and access them using the IConfiguration
object. To demonstrate that, firstly, let us add a new configuration file named CustomConfig.json
to the project. And this configuration file will have a single configuration section CustomConfig
.
{
"CustomConfig": "Added custom config"
}
Secondly, we will update the Program
class’s CreateHostBuilder
method to add the extension method ConfigureAppConfiguration
. And to the IConfigurationBuilder
delegate, we will call the AddJsonFile
extension method to add the custom configuration file.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public static class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.ConfigureAppConfiguration(c =>
c.AddJsonFile("CustomConfig.json"));
});
}
}
Now I will go and update the Startup
class’s ConfigureServices
method to access the newly added custom configuration value.
Console.WriteLine(Configuration["CustomConfig"]);
Now if I run the application, I will see the value of CustomConfig
node is printed in the console output.
Overriding configuration with arguments
We can update the configurations from the arguments passed to the application. To demonstrate this, let us say, we want to override the Version
number of the appsettings.development.config
from the arguments passed to the application.
We can set up the arguments in the Project Properties window in the Debug tab as shown below.
Once we do that, we can update the CreateHostBuilder
method of Program
class to replace the configuration value from the arguments passed.
To do that firstly, I will create a new Dictionary
instance, where I will create a mapping of the argument keys with the configuration keys.
var replacement = new Dictionary<string, string>
{
{"-v", "Version" }
};
Secondly, I will call the AddCommandLine
extension method on the IConfigurationBuilder
instance to replace the configurations with command line arguments.
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Config.Demo
{
public static class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
var replacement = new Dictionary<string, string>
{
{"-v", "Version" }
};
webBuilder.UseStartup<Startup>()
.ConfigureAppConfiguration(c =>
c.AddJsonFile("CustomConfig.json")
.AddCommandLine(args, replacement));
});
}
}
Now if we run our application, we will see that the value of Version
in the console output from the Startup
class where we are accessing it through Configuration.GetValue
will print out as 2.0 now.
Conclusion
As you can see, configuration management in ASP.NET Core is extremely streamlined which makes using configurations a breeze in ASP.NET Core applications.
I have captured the entire process in the YouTube video here.
The source code for this blog post is available in my GitHub repo here.