Dynamic ASP.NET Core Configurations 🗒️ With Consul KV

Background

Usually, the configurations in .NET and .NET Core apps are stored in the configuration files, such as App.config and Web.config, or appsettings.json; however, all of them have some disadvantages.
  1. hard-coded configuration files
  2. difficult to manage
For example, if we need to frequently change the configuration files due to some reasons, how can we solve this problem? We cannot modify them one by one if there are lots of machines!
 
What we want is managing the configuration in one place, the "configuration center". Here are some awesome projects that can help us solve this problem, such as Apollo, Consul, etc.
 
And in this article, I will introduce three ways using Consul KV store.

What is Consul

 Dynamic ASP.NET Core Configurations With Consul KV
Consul is a distributed, highly available, and data center-aware solution to connect and configure applications across a dynamic, distributed infrastructure.
 
We will use one of its features named Key/Value Storage. It's a flexible key/value store that enables storing the dynamic configuration, feature flagging, coordination, leader election, and more. The simple HTTP API makes it easy to use anywhere.
 
Calling REST API Directly
 
In this way, we read configuration from Consul KV store via REST API. What we need to configure in our project is the address of Consul.
 
Creating an ASP.NET Core Web API project at first.
 
Here, we will use a package named Consul to handle the REST API, which can make it easier!
 
Add Consul package via NuGet.
  1. Install-Package Consul -Version 0.7.2.6  
 In order to config the address of Consul, we need to modify the appsettings.json file.
  1. {  
  2.   "Logging": {  
  3.     "LogLevel": {  
  4.       "Default""Warning"  
  5.     }  
  6.   },  
  7.   "AllowedHosts""*",  
  8.   "Consul": {  
  9.     "Host""http://127.0.0.1:8500"  
  10.   }  
  11. }  
 After configuring the address, we should add an extesion for Consul which can help us to use the ConsulClient with DI.
  1. public static IServiceCollection AddConsul(this IServiceCollection services, IConfiguration configuration)  
  2. {              
  3.     services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>  
  4.     {  
  5.         //consul address  
  6.         var address = configuration["Consul:Host"];  
  7.         consulConfig.Address = new Uri(address);  
  8.     }, null, handlerOverride =>  
  9.     {  
  10.         //disable proxy of httpclienthandler  
  11.         handlerOverride.Proxy = null;  
  12.         handlerOverride.UseProxy = false;  
  13.     }));  
  14.     return services;  
  15. }  
The next thing we should do is call the ConsulClient where you need it!
 
The following code shows you how to use it in Controller. 
  1. [Route("api/[controller]")]  
  2. [ApiController]  
  3. public class ValuesController : ControllerBase  
  4. {  
  5.     private readonly IConsulClient _consulClient;  
  6.   
  7.     public ValuesController(IConsulClient consulClient)  
  8.     {  
  9.         this._consulClient = consulClient;  
  10.     }  
  11.      
  12.     [HttpGet("")]  
  13.     public async Task<string> GetAsync([FromQuery]string key)  
  14.     {  
  15.         var str = string.Empty;  
  16.         //query the value  
  17.         var res = await _consulClient.KV.Get(key);  
  18.   
  19.         if (res.StatusCode == System.Net.HttpStatusCode.OK)  
  20.         {  
  21.             //convert byte[] to string  
  22.             str = System.Text.Encoding.UTF8.GetString(res.Response.Value);  
  23.         }  
  24.   
  25.         return $"value-{str}";  
  26.     }  
  27. }  
 Let's take a look at the result.
 
Before adding a key in Consul KV, we cannot get the value. 
 
Dynamic ASP.NET Core Configurations With Consul KV 
 
After creating and modifying, we can get the latest value in real-time.
 
Dynamic ASP.NET Core Configurations With Consul KV 
 
However, this way cannot combine with the configuration system that ASP.NET Core provides. In the next section I will introduce how to combine with ASP.NET Core's configuration.
 
Combine With Configuration System
 
Most of the time, we use IConfiguration, IOptions<T> , IOptionsSnapshot<T> and IOptionsMonitor<T> to read the configuration in ASP.NET Core. How can we combine them with consul?
 
Here I suggest using Winton.Extensions.Configuration.Consul. It's an amazing project that adds support for configuring .NET Core applications using Consul.
 
Due to using this project, things will become easier! It helps us to read and reload the configurations in consul KV.
 
Let's take a look at how to do it.
 
Install this package via NuGet at first.
  1. Install-Package Winton.Extensions.Configuration.Consul -Version 2.1.2  

Prepare before we configure with Consul.

Create a setting class that shows the usages of Options.
  1. public class DemoAppSettings  
  2. {  
  3.     public string Key1 { getset; }  
  4.     public string Key2 { getset; }  
  5. }  
Configuring the options in Startup class:
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     services.Configure<DemoAppSettings>(Configuration.GetSection("DemoAppSettings"));  
  4.     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  5. }  

Note
We don't need to modify the appsettings.json file.

Calling the configuration in controller.
  1. [Route("api/[controller]")]  
  2. [ApiController]  
  3. public class ValuesController : ControllerBase  
  4. {  
  5.     private readonly IConfiguration _configuration;  
  6.     private readonly DemoAppSettings _options;  
  7.     private readonly DemoAppSettings _optionsSnapshot;  
  8.   
  9.     public ValuesController(
  10.          IConfiguration configuration
  11.          , IOptions<DemoAppSettings> options
  12.          , IOptionsSnapshot<DemoAppSettings> optionsSnapshot)  
  13.     {  
  14.         this._configuration = configuration;  
  15.         this._options = options.Value;  
  16.         this._optionsSnapshot = optionsSnapshot.Value;  
  17.     }  
  18.   
  19.     // GET api/values  
  20.     [HttpGet]  
  21.     public ActionResult<IEnumerable<string>> Get()  
  22.     {  
  23.         return new string[] {_configuration["DemoAppSettings:Key1"], _options.Key1, _optionsSnapshot.Key1 };  
  24.     }          
  25. }  

At last, we should configure Winton.Extensions.Configuration.Consul. It provides two ways to do it, one is in Program class, the other on is in Startup class.

The following code demonstrates how to configure in Program class.
  1. public static void Main(string[] args)  
  2. {  
  3.     var cancellationTokenSource = new CancellationTokenSource();  
  4.     WebHost  
  5.         .CreateDefaultBuilder(args)  
  6.         .ConfigureAppConfiguration(  
  7.             (hostingContext, builder) =>  
  8.             {  
  9.                 builder  
  10.                     .AddConsul(  
  11.                         "App1/appsettings.json",  
  12.                         cancellationTokenSource.Token,  
  13.                         options =>  
  14.                         {  
  15.                             options.ConsulConfigurationOptions =  
  16.                                 cco => { cco.Address = new Uri("http://127.0.0.1:8500"); };  
  17.                             options.Optional = true;  
  18.                             options.ReloadOnChange = true;  
  19.                             options.OnLoadException = exceptionContext => { exceptionContext.Ignore = true; };  
  20.                         })  
  21.                     .AddEnvironmentVariables();  
  22.             })  
  23.         .UseStartup<Startup>()  
  24.         .Build()  
  25.         .Run();  
  26.   
  27.     cancellationTokenSource.Cancel();  
  28. }   
Here is the result after running up this project.
 
We create a KV in Consul, and visit http://localhost:5000/api/values. We can read the configuration from Consul.
 
Dynamic ASP.NET Core Configurations With Consul KV 
 
After modifying key1's value from 1111 to 1122, refresh the browser; the values were changed but IOptions<DemoAppSettings>
 
Dynamic ASP.NET Core Configurations With Consul KV
 
So, when we use this way to handle the configuration, we should not use IOptions<T> in our code! This is an important tip.
 
There also has another way to make the configurations dynamically.

Consul Template

Consul Template is a component that provides a convenient way to populate values from Consul into the file system.
 
For more information, please visit its GITHUB page
 
Most of the times, the configurations in appsettings.json, appsettings.xml, appsettings.ini or other files are hard-coded, however, we can utilize Consul’s KV store to avoid hard-coding configurations in the file so we don’t have to change the file each time if any modification is needed in application settings.
 
Those file updates should be done by the Consul tool. We can replace any file instead of this JSON file as the procedure works the same for all files.
 
The following picture shows how to do it.
 
Dynamic ASP.NET Core Configurations With Consul KV 
 
Create an ASP.NET Core Web API project at first.
 
Here is something like before.
  1. public class DemoAppSettings  
  2. {  
  3.     public string Key1 { getset; }  
  4.     public string Key2 { getset; }  
  5. }  
  6.   
  7. public class Startup  
  8. {  
  9.     public void ConfigureServices(IServiceCollection services)  
  10.     {  
  11.         //config   
  12.         services.Configure<DemoAppSettings>(Configuration.GetSection("DemoAppSettings"));  
  13.         services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  14.     }  
  15. }  
  16.   
  17. [Route("api/[controller]")]  
  18. [ApiController]  
  19. public class ValuesController : ControllerBase  
  20. {  
  21.     private readonly IConfiguration _configuration;  
  22.     private readonly DemoAppSettings _options;  
  23.     private readonly DemoAppSettings _optionsSnapshot;  
  24.   
  25.     public ValuesController(IConfiguration configuration, IOptions<DemoAppSettings> options, IOptionsSnapshot<DemoAppSettings> optionsSnapshot)  
  26.     {  
  27.         this._configuration = configuration;  
  28.         this._options = options.Value;  
  29.         this._optionsSnapshot = optionsSnapshot.Value;  
  30.     }  
  31.   
  32.     // GET api/values  
  33.     [HttpGet]  
  34.     public ActionResult<IEnumerable<string>> Get()  
  35.     {  
  36.         //to show the result of different usges   
  37.         return new string[] { _configuration["DemoAppSettings:Key1"], _options.Key1, _optionsSnapshot.Key1 };  
  38.     }          
  39. }  
Now, we should turn to consul-template.
 
Creating a configuration file
 
Creating a configuration file that tells `consul-template` the source and destination file name.
 
`consul-template` will read configurations and convert the template file by the adding values referenced in the template file from the Consul’s Key-Value store and generate the configuration file for the application.
 
Here, we create a configuration file named `appsettings.tpl`, and its content is as follows.
  1. {{ key "App1/appsettings.json" }}   
starting consul-template,
  1. consul-template -template "yourpath/appsettings.tpl:yourpath/appsettings.json"  
Here is the result.
  
Dynamic ASP.NET Core Configurations With Consul KV 
 
After modifying the value in Consul KV,  appsettings.json was changed in real-time.  
 
 Dynamic ASP.NET Core Configurations With Consul KV
 
And the behavior of accessing the browser is the same as in the second way.

Summary

This article showed you the three ways to our dynamic ASP.NET Core project configuration with Consul KV.
  1. Calling REST API Directly
  2. Winton.Extensions.Configuration.Consul
  3. Consul Template
And I will suggest you use the second and third way. Because those two ways are easier.
 
I hope this can help you! 
 
Related Articles