.NET

Option patterns with custom configuration provider in .NET

How to develop reloadable provider for Option Patterns

Tsuyoshi Ushio

--

Enjoy Option patterns

I was wondering how to write a custom configuration provider with IOptionsMonitor<T> that means, some value will be loaded after a DI configuration finished. I had following questions.

  • How to write a custom configuration provider?
  • How can we reload the configuration value using IOptionsMonitor<T>?
  • How to create an entry that requires multiple hierarchy json structure?

Options pattern

The options pattern uses classes to provide strongly-typed access group of related settings. For more details refer to the following link

Options pattern in .NET | Microsoft Docs

Overview

You have appsettings.json and want to get the value through your custom class object without parsing by yourself.

{
"extensions": {
"kafka": {
"MaxPollingInterval": 12
},
"http": {
"MaxConcurrentRequest": 100
}
}
}

You can define the following class to get the kafka section.

public class KafkaOptions
{
public int MaxPollingInterval { get; set; }
public int AutoCommitInterval { get; set; } = 3;
}

You can use the extension method to get the configuration from the file. If you are not familiar with the IConfiugrationBuilder interface and DI for the .NET, refer to Dependency injection in .NET | Microsoft Docs .

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: true);
}

AddJsonFile() method enables to get the configuration from a configuration file. I’ll explain the other parameter later. You need to configure the KafkaOptions to register the object to be hydrated.

.ConfigureServices((context, services) => {
var configurationRoot = context.Configuration;
services.Configure<KafkaOptions>(
configurationRoot.GetSection("extensions").GetSection("kafka")
);

You are ready to get the value through the DI system! You have the following Service class. You can use IOptions<T> interface to fetch the object.

public class FunctionsHostService : IHostedService
{
private readonly IConfiguration _configuration;
private readonly KafkaOptions _kafkaOptions;
public FunctionsHostService(
IConfiguration configration, IOptions<KafkaOptions> kakfaOptions)
{
_configuration = configration;
_kafkaOptions = kafkaOptions.Value;
}

Then register the service.

.ConfigureServices(services =>
{
services.AddHostedService<FunctionsHostService>();
});

Hit F5 for running the service. You can see the _kafkaOptions has the configuration value.

What happens?

You can see the IConfigration object. The object includes several Providers. In this case, you can find JsonConfigurationProvider that is added by AddJsonFile() extension method. You can find the appsettings.json values with the key value pair. The json file has hierarchical structure. It is expressed by the key with the delimiter of : . For example.
Key: extensions:kafka:MaxPollinginterval Value: 12.

IConfiugration

The object that has IOptions<T> automatically read the configuration value from the IConfiguration object value.

Reload the config value

You might want to update the configuration value. However, IOptions<T> is registered as singleton. The hydration happens only one time. If you want to update the value, you need to use IOptionsSnapshot<T> or IOptionsMonitor<T> . They are registerd as scoped and singleton respectively. However, IOptionsMonitor<T> will have a feature to update the value. Let’s experience the feature.

Go back to the configuration of AddJsonFile() , you might notice the reloadOnChange options. That enable to update the configuration if the appsettings.json has been changed. optional means, If the appsettings.json is optional or not.

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: true,
reloadOnChange: true);
}

Update the interface of the Service to use IOptionsMonitor<T>

public class FunctionsHostService : IHostedService
{
private readonly IConfiguration _configuration;
private readonly IOptionsMonitor<KafkaOptions> _kafkaOptions;
public FunctionsHostService(
IConfiguration configration, IOptionsMonitor<KafkaOptions> kakfaOptions)
{
_configuration = configration;
_kafkaOptions = kafkaOptions;
}

The value of the IOptionsMOnitor<T> isCurrentValue() method. Run the application and see the value change. I update the appsettings.json during the execution. Looks reflected.

appsettings.json (Modifled)
MaxPollingInterval is updated

What if there are multiple providers?

Order of the Provider

If you have multiple providers, the last one wins if it has the same key.

How to develop a provider that enables reload feature?

I wanted to develop a provider that enables reload feature. The provide need to reload the value after the DI configuration has been finished. IOptionMonitor<T> will do nothing if you don’t update the IConfiguration object. For updating IConfiguration object, you need to update the value.

Develop a custom provider

Before moving to the reload feature, I’d like to explain how to develop custom provider. We have three steps.

  1. Create a configuration provider subclass.
  2. Create a Configuration Source
  3. Register the Configuration Source with DI

Let’s see the code.

ConfigurationProvider

Implement the Provider that hydrates the values the read from a data source. It requires to implementIConfigurationProvider , however ConfigurationProvider class implement all of them. We overwrite a method Load()

IConfigurationProvider ‘s interface is following.

IConfigurationProvider interface

The ConfigurationProvider has Data that is IDictionary<string, string> You need to update the dictionary on your custom code.

For example, if you want to read values from a DataSource, you can do like this.

public class ExtensionsConfigurationProvider : ConfigurationProvider    {        
public override void Load()
{
Data.Add(Constants.ExtensionsConfigurationKey, JsonConvert.SerializeObject(ExtensionsConfigurationDataSource.GetJson()));
}
}

Configuration Source

Implement IncofigrationSource It needs to implement Build() method for factory the IConfigurationProvider .

public class ExtensionsConfigurationSource : IConfigurationSource    {       
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new ExtensionsConfigurationProvider();
}

Register the Provider

Provide an Extension method for registering the provider. Just add the ConfigurationSource object.

public static class ConfigurationBuilderExtensions    
{
public static IConfigurationBuilder AddExtensionsConfigration(this IConfigurationBuilder builder)
{
return builder.Add(new ExtensionsConfigurationSource());
}
}

Add it on the DI setting.

.ConfigureAppConfiguration(config =>            
{
config.AddExtensionsConfigration();
})

That’s it.

How to implement the reload feature?

The responsibility to read the value is, provider. We need to implement it on the provider side. We need to do two things.

  1. Update the Data dictionary.
  2. Call OnReload() method.

I added the callback to the DataSource that enables call Load() method when it has a change. You need to update the Data and call OnReload() .

public class ExtensionsConfigurationProvider : ConfigurationProvider    {        
public ExtensionsConfigurationProvider()
{
ExtensionsConfigurationDataSource.Subscribe(nameof(ExtensionsConfigurationProvider), Load);
}
public override void Load()
{
if (Data.ContainsKey(Constants.ExtensionsConfigurationKey))
{
Data.Remove(Constants.ExtensionsConfigurationKey);
}
Data.Add(Constants.ExtensionsConfigurationKey, JsonConvert.SerializeObject(
ExtensionsConfigurationDataSource.GetJson()));
}
OnReload();
}

OnReload() method generates new token in the ConfigurationProvider . IConfiguration object watch the change of the token, once, it has been changed, the IConfiugration update it’s value.

Represent hierachical keys

We need to implement the keys that can be used by IOptions We need to provide extensions:kafka:MaxPollinginterval style keys. Update the class for using it. The concept is simple, however, the code is pretty long. I’d like to share it with my sample application’s commit.

Use IOptionsMonitor<T> to read the value · TsuyoshiUshio/ConfigurationSpike@c45697b (github.com)

Define a class that represent the appsettings.json structure. I define it as kafka to match the key, however, you can make it Kafka for fit the .NET coding convention.

public class ExtensionsOptions
{
public HttpOptions http { get; set; }
public KafkaOptions kafka { get; set; }
public EventHubsOptions eventHubs { get; set; }
}
Hydrate the values with the customer provider for IConfiguration
The options are hydrated.

Sample project

You can find the Sample project on this repository.

TsuyoshiUshio/ConfigurationSpike: Spike Solution for Enabling Aggregated Config object setup with Host. (github.com)

Conclusion

Investigated the options pattern and how to implement reloadable providers. Once understanding the structure, it is pretty neat. I hope this article helps someone who wants to understand option patterns and providers.

Resources

Using Configuration and Options in .NET Core and ASP.NET Core Apps | Pluralsight

Very nice Pluralsight course that explains Options pattern .

Options pattern in .NET | Microsoft Docs
Official documentation for the options pattern.

Implement a custom configuration provider in .NET | Microsoft Docs

The official documentation for the custom configuration provider.

--

--