In one of my previous posts, Hiding Secrets in appsettings.json – Using a Bridge in your ASP.Net Core Configuration (Part 4) I had a comment from Anderson asking if I had considered IConfigureOptions
I had not come across the interface, so with an example provided by Anderson, I started to look further into it. Andrew Lock has post on his blog that describes how an implementation works which was a starting point for me.
In your implementation of IConfigureOptions
In the example below, I have:
- a class MySettings which I will bind the configuration section to.
- three implementations of IConfigureOptions
which change the Setting1 string in different ways.
- an interface IDateTimeResolver and class implementation to demonstrate the DI container injecting a resolved instance into the last of the IConfigureOptions
implementations (and also serves as a reminder that using DateTime.Now directly in your code is evil and should be wrapped in another class so it can be substituted when unit testing)
The interface/implementation mappings are registered with the DI container in the Startup class. Note the Confgure
What is interesting, is that multiple classes can implement the interface and, when registered with the DI container, be applied in the order they have been registered.
When I first saw this, I was confused as my understanding of DI registration was that the last registration is the one that is used to return the resolved instance by the DI container, so how could multiple registrations be applied? Of course, the penny dropped in that whatever is using these instances is not asking for the DI container to resolve a single instance, but is somehow iterating over the relevant registrations and resolving each registration in turn then calling the .Configure method.
This pretty much confirmed the theory that the services collection is being iterated over, except you will notice that the constructor takes a parameter of IEnumerable<IConfigureOptions<TOptions>>. So what is resolving these multiple instances?
Well it turns out to be the DI Container itself! I will go into more detail in a future post (as I don't want to deviate from the main topic of this post)*, but in short, if your constructor takes an IEnumerable
*Update - Since the post was written, Steve Gordon has written a great post that goes into more detail about the DI container and resolving multiple implementations - see ASP.NET Core Dependency Injection – Registering Multiple Implementations of an Interface.
So in the OptionsFactory instance that will be created by ASP.Net using the DI container (where TOptions is MySettings, the class I have set up for configuration binding), the DI container will resolve the parameter of IEnumerable<IConfigureOptions<MySettings>> to our three registered implementations of IConfigureOptions
Therefore to demonstrate this, we will start with an appsettings.json that looks like this:
We can step through the three implementations as they are called by the factory Create method to see how the Settings1 value in MySettings is updated:
The eagle eyed will have spotted that in the OptionsFactory, there is a check on each instance of IConfigureOption
This is very similar to IConfigureOption
Once each of the registered implementations of IConfigureOption
I plan on covering these two interfaces in a future post as I don't want to make this post too long.
At first glance, this approach is similar to the bridge pattern approach I have described in my earlier posts in that the responsibility for changing the state values is delegated to an outside class.
However it is not a bridge as the properties of the underlying configuration instance are being changed. A bridge will intercept requests and then change the return value, leaving the underlying configuration instance unchanged.
When using the IConfigureOptions
My personal opinion is that this not as clean as the bridge approach (but I guess I am biased).
I prefer to try to avoid potentially unexpected mutation of objects and instead return either a new instance of the object with property values copied from the existing into the new one or a substitute (e.g. the bridge). In other words, a functional approach where the input object is treated as immutable.
This leaves the original instance of the class in its initialised state which may be required by other functionality in the application that does not want the updated values or can be used by a different bridge class.