With ASP.NET core you can bind configurations values from a variety of configuration providers be it JSON, memory configuration provider or your could write you own custom provider. The framework allows you bind configuration from these sources to concrete instances of a class with properties set the configuration values using the IOptions<T>
method.
The IOptions<T>
allows you register the instantiated configuration class.
A brief recap would be to load upload the config details say for example we are getting these details from the appsettings.json
For .NET 6 the appsettings.json
is loaded up automatically.
For this example the appsettings.json
would look like this
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"CustomConfigValue": {
"CustomProperty": "value here"
}
}
In order to bind the CustomConfigValue
node to class/record instance we would need to create a class/record
public class CustomConfigValue
{
public string CustomProperty { get; set; }
}
then to bind JSON node CustomConfigValue
we would do this in the Program.cs
builder.Services.Configure<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));
Then in the class/controller constructor we set the value from IOptions<T>
to the a class property of the same type
private readonly CustomConfigValue _customConfig;
public CustomConfigClass(IOptions<CustomConfigValue> customConfigValue)
{
_customConfig = customConfigValue.Value;
}
There are a few benefits to using this method, for one it gives us the ease when mocking out the configuration details in our tests, another benefit is configuration reloading when config values change in the JSON file .
Another approach In some case the use of IOptions<T>
isn't beneficial and there are a lot of posts about how to bind directly to object instances instead. From our previous example what we would be doing different is that we would be doing this in the Program.cs
CustomConfigValue customConfig = new ();
builder.Configuration.Bind(nameof(CustomConfigValue), customConfig);
builder.Services.AddSingleton(typeof(CustomConfigValue), customConfig);
In the class/controller constructor we would be able to access the CustomConfigValue
like this
private readonly CustomConfigValue _customConfig;
public CustomConfigClass(CustomConfigValue customConfigValue)
{
_customConfig = customConfigValue;
}
This can be abstracted away from the Program.cs
by creating a service extension then doing the work like this
public static class ConfigurationExtensions
{
public static void AddCustomConfigValue<T>(this IServiceCollection services, IConfigurationSection configSection) where T : class
{
T setting = configSection.Get<T>();
services.TryAddSingleton<T>(setting);
}
}
In the Program.cs
we do this to bind the configuration to the class object
builder.Services.AddCustomConfigValue<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));
This is all well and good, and covers most use cases, but one thing I find that this method has is yes, you guess it, doesn't bind to an interface. I really love the idea of abstractions, I think it's a concept everyone should apply in every part of the life, but I digress.
In order to bind to an interface let's create another method in the extension class
public static void AddCustomConfigValue<InterfaceT, T>(this IServiceCollection services, IConfigurationSection configSection)
where T : InterfaceT
where InterfaceT : class
{
InterfaceT setting = configSection.Get<T>();
services.TryAddSingleton(setting);
}
We would add an interface
public interface ICustomConfig
{
string CustomProperty { get;}
}
Then have the CustomConfigValue implement the interface
public class CustomConfigValue : ICustomConfig
In the Program.cs
we would do this to bind the configuration to the object
builder.Services.AddCustomConfigValue<ICustomConfig, CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));
And in our constructor we would be able to access the configuration with
private readonly ICustomConfig _customConfigViaInterface;
public CustomConfigClass(ICustomConfig customConfigViaInterface)
{
_customConfigViaInterface= customConfigViaInterface;
}
You could do this to maintain some level of abstraction and aid testing via the binding of the object to an interface and having only get
access via the interface.
Another thing to consider is to use the IOptions<T>
but bind to a record. The idea behind this is to create an immutable configuration object, once instantiated properties can't be reassigned and enforced at compile time.
Top comments (0)