DEV Community

Ifetayo Agunbiade
Ifetayo Agunbiade

Posted on

Binding ASP.NET configuration to an interface

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

then to bind JSON node CustomConfigValue we would do this in the Program.cs

builder.Services.Configure<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the Program.cs we do this to bind the configuration to the class object

builder.Services.AddCustomConfigValue<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

We would add an interface

public interface ICustomConfig
{
    string CustomProperty { get;}
}
Enter fullscreen mode Exit fullscreen mode

Then have the CustomConfigValue implement the interface

public class CustomConfigValue : ICustomConfig
Enter fullscreen mode Exit fullscreen mode

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)));
Enter fullscreen mode Exit fullscreen mode

And in our constructor we would be able to access the configuration with

private readonly ICustomConfig _customConfigViaInterface;

public CustomConfigClass(ICustomConfig customConfigViaInterface)
{
    _customConfigViaInterface= customConfigViaInterface;
}
Enter fullscreen mode Exit fullscreen mode

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)