Creating a Bridge to your ASP.Net Core Configuration from your Controller or Razor Page (Part 2)

In my previous blog post, I set the scene to give some background as to the relative merits of using the Options pattern vs binding a configuration object to a singleton in the DI container.

To summarise

  • IOptionsSnaphot allows the configuration to be changed without having to restart the application in order for new requests to make use of the changed values. However, the DI container will present the configuration as IOptions or IOptionsSnaphot which means that any assembly that makes using of the configuration will need to refer to the Microsoft.Extensions.Options Nuget package. Some feel that this is an overhead and is harder to test than a reference to just T (where T is the bound configuration object)
  • Binding the configuration object T in the service setup and then storing it in a singleton allows method signatures to just use T as a parameter which is cleaner, but loses the ability to dynamically react to changes in the configuration (E.g. the appsettings.json being changed)

For this post, I originally planned to launch into a full blown discussion of creating a bridging class between the controller and the configuration setting object to mask the use of IOptions.

However, before doing that, I noticed a comment at the bottom of Rick Strahl’s blog post from Todd Menier pointing out that a very simple bridge could be created by using an anonymous function, which seemed a good way to start to describe the basics of creating a bridge between the controller and the configuration before going into the more complex implementation of using abstractions and classes.

Before getting into creating the bridge, a quick recap of why the bridge is required.

An Example of Out-of-the-Box IOptions implementation

Say you have a class called MyAppSettings that you want to bind configuration data to, you will have a class that looks like this:

public class MyAppSettings
{
public string ApplicationName { get; set; }
public int CountOfItems { get; set; }
}

view raw
MyAppSettings.cs
hosted with ❤ by GitHub

Which in turn you add to your appsettings.json as this:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"MyAppSettings": {
"ApplicationName": "Hello World",
"CountOfItems": 10
}
}

view raw
appsettings.json
hosted with ❤ by GitHub

In order to wire up the two, you will have a Startup.cs class that looks like this:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
}

view raw
Startup.cs
hosted with ❤ by GitHub

The first important line is services.AddOptions() which is required for IOptions to work.

The second is services.Configure(Configuration.GetSection(“MyAppSettings”)) which takes the MyAppSettings section that has been read from the configuration sources (in our case appsettings.json, but this may have been overridden by other sources) and binds it to an instance of MyAppSettings.

Looking at the source code for Configure(IConfiguration config) shows that behind the scenes, two singletons are registered: ConfigurationChangeTokenSource and NamedConfigureFromConfigurationOptions.

The first of these monitors the configuration for changes and is used when a parameter is defined as being of type IOptionsSnaphot. This allows changes to be sent to the controller without the need to restart the application. The second handles binding the object that holds the configuration values as the class instance.

With the binding set up, in your controller, you can have a constructor that looks like this:

public class HomeController : Controller
{
private readonly MyAppSettings _settings;
public HomeController(IOptionsSnapshot<MyAppSettings> settings)
{
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
}
public IActionResult Index()
{
return View(_settings);
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

view raw
HomeController.cs
hosted with ❤ by GitHub

Note, that in order to receive the MyAppSettings instance, the constructor parameter must be either of type IOptions or IOptionsSnapshot. The difference being that the former only gets the settings as they were when the application started, but the latter reads the latest version of the settings even if they changed after the application has started.

It is this extra bit of orchestration that some people don’t like as it means that any unit tests must mock the IOptions interface and provide an implementation of the Value method to get to the MyAppSettings object that is actually of interest.

Now, if it was purely just about MVC controllers and razor pages, I am personally not too hung up on this. However, it becomes a bit more complicated if the configuration settings are required for the constructor of a class in some other assembly as it then means that the other assembly needs to have a reference to the Microsoft.Extensions.Options Nuget package. This in turn could start a dependency sprawl across multiple assemblies.

Intercepting with a Bridge

To avoid this, a bridge is required that can accept IOptions or IOptionsSnapshot as a parameter, but presents itself as MyAppSettings (or an abstraction of it).

In my next post, I intend to present a solution using a bridging class that can accept other parameters to do more than just abstract away from the options pattern, by adding functionality such as decryption.

In the meantime, a way of simply getting the options pattern out of the way is to add another service to the DI container, this time a transient that uses an anonymous lambda function to get the value out of the options pattern and present that (as suggested by Todd).

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
// use a lambda to act as the bridge between requesting an instance of MyAppSettings but
// getting it from IOptionsSnapshot<MyAppSettings>
services.AddTransient<MyAppSettings>((opt) => opt.GetService<IOptionsSnapshot<MyAppSettings>>().Value);
}

view raw
Startup.cs
hosted with ❤ by GitHub

This then allows the controller (or any other class that needs access to the configuration settings) to just need the MyAppSettings class as the parameter:

public class HomeController : Controller
{
private readonly MyAppSettings _settings;
public HomeController(MyAppSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public IActionResult Index()
{
return View(_settings);
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

view raw
HomeController.cs
hosted with ❤ by GitHub

This lets the DI container deal with the bridging and keeps the clients ignorant of the options pattern.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s