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.
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:
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:
We will now divide it up, starting with the interface:
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:
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.
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.
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.