Configuring your .NET Configuration using IOptions
This blog post goes over a clean way to access your configuration variables in your .NET project instead of passing IConfiguration directly into services. The thing I don't like about passing IConfiguration directly into services is that all the app settings were available to the service even if it doesn't concern the service. As a result, this adheres to the Interface Segregation Principle and Separation of Concerns.
Typically what I see a lot is the IConfiguration is injected and then the key is hardcoded. The service has access to settings that it probably shouldn't have and does not need. And secondly, the key is hardcoded. The issue with that is that if the key changes, the app would still compile and therefore it would be very difficult to initially spot this until something stops working on your, hopefully, dev or QA environment or even worse, production.
The Solution - The Options Pattern
Thankfully there is a solution to this and we can clean this up by using the Options Pattern. This is very easy to set up and I will leave code examples below.
Firstly we need to define the variables in the app settings. Typically it would look something like the below.
Next, we need to create a class that maps exactly to our properties. You will see the string "Section". This should be the key of the object.
And lastly, you will need the configure this class to map to your app settings. This goes in the Startup.cs file in the ConfigureServices method. I normally put this near the top of the method so that it is injected correctly to the services using these settings,
Now we are ready to use these settings in our services. We have to inject this into the service constructor using IOptions. The options pattern uses classes to provide strongly typed access to our settings. You will notice that I am using IOptions<>, there are two more types available, IOptionsSnapshot<> and IOptionsMonitor. I will explain why you may want to use them later.
And now to use this, we can simply use the _jwtSettings like the below. If you remember, this class has 3 properties that we can access and this is all these service needs. It doesn't need to know about any other settings. If it did, we could just inject that into the constructor. This then makes our services coherent and clean.
IOptions<>, IOptionsSnapshot<>, IOptionsMonitor
There are 3 ways you can access their settings and that is by using either of the above, however, they have extra functionality. I have listed the differences below and this can also be found in the Microsoft Documentation. The main differences are the lifetimes of the types, IOptionsSnapshot being scoped and IOptionsMonitor being a singleton and as a result, the updating of the configurations takes place at different times.
- Use
IOptions<T>
when you are not expecting your config values to change. - Use
IOptionsSnaphot<T>
when you are expecting your values to change but want it to be consistent for the entirety of a request. - Use
IOptionsMonitor<T>
when you need real time values as you can setup the OnChange method to update the config object.
Below is a summary from the Microsoft documentation.
- Does not support:
- Reading of configuration data after the app has started.
- Named options
- Is registered as a Singleton and can be injected into any service lifetime.
- Is useful in scenarios where options should be recomputed on every request. For more information, see Use IOptionsSnapshot to read updated data.
- Is registered as Scoped and therefore cannot be injected into a Singleton service.
- Supports named options
- Is used to retrieve options and manage options notifications for
TOptions
instances. - Is registered as a Singleton and can be injected into any service lifetime.
- Supports:
- Change notifications
- named options
- Reloadable configuration
- Selective options invalidation (IOptionsMonitorCache<TOptions>)
I hope this has given you a little bit of insight on how to properly manage your configuration by splitting the app settings out and then mapping them to classes. The app settings and classes, as a result, end up being fine-grained and this allows you to adhere to the Interface Segregation Principle and Separation of Concerns principle and makes your code much more readable.