.NET
Option patterns with custom configuration provider in .NET
How to develop reloadable provider for 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.
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.
What if there are multiple providers?
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.
- Create a configuration provider subclass.
- Create a Configuration Source
- 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.
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.
- Update the
Data
dictionary. - 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; }
}
Sample project
You can find the Sample project on this repository.
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.