Configuring your .NET Configuration using IOptions

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.

var connectionString = Configuration.GetSection("ConnectionString:DefaultConnection");
Getting app settings via IConfiguration

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.

{
  "JwtSettings": {
    "Issuer": "https://issuer.com",
    "SigningKey": "MySigningKey",
    "Audience": "Audience"
  }
}
appSettings.json

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.

public class JwtSettings
{
    public const string Section = "JwtSettings";
    public string SigningKey { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
}
The settings class

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,

services.AddOptions<JwtSettings>().BindConfiguration(JwtSettings.Section);
Startup.cs

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.

private readonly JwtSettings _jwtSettings;

public JwtAuthenticationHandler(IOptions<JwtSettings> jwtSettings)
{
    _jwtSettings = jwtSettings.Value;
}
The Service

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.

public void ValidateJWT()
{
    var issuer = _jwtSettings.Issuer;
}
The Service

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.

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<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.