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

This is the third in my series of posts looking at how to remove the need for controllers and razor pages to have knowledge of the options pattern in ASP.Net Core.

If you have arrived here from a search engine or link, I would recommend reading the previous two posts which set the background before coming back to this post.

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

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

In Part 2, I looked at how a lambda expression could be used to act as a bridge between the IOptionsSnapshot and T by creating an additional DI service:

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

If all you are concerned with is ensuring that your controller or razor page does not need to refer to the options pattern, then this is a suitable solution.

However, you may have the need to do something more exotic with the configuration settings such as perform some transformation such as decrypting some data or would like to perform  a validation of the settings before invalid values get injected into a class. This is achievable using the lambda, but it becomes somewhat messy.

Instead, the approach I will describe in this post will be to split the MyAppSettings class from the previous post out into three parts:

  • An interface that defines the properties as read only values IMyAppSettings
  • A settings reader class, MyAppSettingsReader,  that implements the interface but also implements setters so that the values can be mapped into an instance by the Configure extension method
  • A bridge class, MyAppSettingsBridge, that takes the IOptionsSnapshot in the constructor and then presents itself as the interface by using the Value method to get the value that has been read from configuration.

The last part of the jigsaw is then to register the bridge as a transient service with the DI container. From therein, the controllers, razor pages and any other class that needs the settings will just need a parameter of type IMyAppSettings.

Splitting the Class Up

In the previous post, the MyAppSettings class looked like this:

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

view raw
MyAppSettings.cs
hosted with ❤ by GitHub

We will now divide it up, starting with the interface:

public interface IMyAppSettings
{
string ApplicationName { get; }
int CountOfItems { get; }
}

view raw
IMyAppSettings.cs
hosted with ❤ by GitHub

Note that the properties have been defined as read-only. Given that the configuration source(s) are read only as far as the code is concerned ( JSON files, XML files, environmental variables etc.), it is unlikely you will have code that would change the values.

Then comes the two implementation of the interface:

public class MyAppSettingsReader : IMyAppSettings
{
public string ApplicationName { get; set; }
public int CountOfItems { get; set; }
}
public class MyAppSettingsBridge : IMyAppSettings
{
private readonly IOptionsSnapshot<MyAppSettingsReader> _optionsConfig;
public MyAppSettingsBridge(IOptionsSnapshot<MyAppSettingsReader> optionsConfig)
{
_optionsConfig = optionsConfig ?? throw new ArgumentNullException(nameof(optionsConfig));
}
public string ApplicationName => _optionsConfig.Value.ApplicationName;
public int CountOfItems => _optionsConfig.Value.CountOfItems;
}

The MyAppSettingsReader is a simple DTO that the Configure extension method can map the configuration settings to – and therefore does need setters as well as the getters.

This class does not strictly need to implement the interface as it is there simply to map settings from the configuration into an object. You could include other properties that may be components that will be combined as a value returned in a property exposed in the interface. E.g. say you have a property that is a database connection string.

The second class is the bridge itself, MyAppSettingsBridge which must implement the interface as it will be used in the DI container. Note the constructor takes IOptionsSnapshot as the parameter and then acts as the go-between for the properties for the interface.

In the example class above, the class is effectively doing the same as the lambda expression from the previous post by calling the Value property on the options object.

However, by using a class, you can add more functionality. Taking the database connection example again, you could have individual properties for the server name, the database name, etc. in the reader class which can be then passed as parameters into a connection string builder inside the bridge class whose result is then exposed as a ConnectionString property in the interface.

In my plans for another post, I will be showing how values could be encrypted in the configuration settings source and then decrypted by the bridge class.

Wiring It All Up

Now we have the interface, reader and bridge, it is time to wire it all up.

Firstly, we will set up the DI container to read the configuration into the reader. This will automatically create a DI service for IOptionsSnapshot. Next we register the bridge as a transient instance of the IMyAppSettings interface.

public class Startup
{
// Other methods omitted for brevity in example
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
// use the reader class to map the configuration
services.Configure<MyAppSettingsReader>(Configuration.GetSection("MyAppSettings"));
// but use the bridge class as the implementation of IMyAppSettings
services.AddTransient<IMyAppSettings, MyAppSettingsBridge>();
}
}

view raw
Startup.cs
hosted with ❤ by GitHub

Why register as Transient?It is up to you really. If registered as a transient, then every call will get the latest version of the configuration injected into it from IOptionsSnapshot. 

If registered as a singleton, the IOptionsSnapshot does not reload the configuration on each request.

If you are not worried about having the ability to read the configuration without restarting the application, use the singleton and also change IOptionsSnapshot to IOptions so that the configuration monitor is not required.

Now we are all set with the DI container, so we can change our controller to take IMyAppSettings as the parameter to the controller.

public class HomeController : Controller
{
private readonly IMyAppSettings _settings;
public HomeController(IMyAppSettings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public IActionResult Index()
{
return View(_settings);
}
}

view raw
HomeController.cs
hosted with ❤ by GitHub

The code for this post and the previous post is available on GitHub at https://github.com/configureappio/configurebridgedemo1

In the next post, I will look at injecting more functionality into the bridge to decrypt some settings before they get injected into the controller.

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