ASP.NET Core - Accessing Configurations Using Options Pattern

Introduction

 
In our previous article ASP.NET Core - Accessing Configuration Settings from Appsettings.json File using IConfiguration, we learned about how to access configuration settings using IConfiguration dependency in Asp.Net Core project, which is very basic level binding. In this article, we will see another better approach to read configuration settings using Options Pattern and how it implements and extends the configuration feature in .Net core.
 

Options Pattern

 
Options Pattern is used to bind a section of configuration settings to the strongly types options classes and add it to the Asp.Net Core Dependency Injection Service Container as singleton lifetime using the "Configure" method of IServiceCollection interface. Once configured, strongly typed Options class can be injected into any service or controller via Dependency Injection using one of available generic Options interfaces IOptions<T>, IOptionsSnapshot<T> and IOptionsMonitor<T>. We will look step-by-step how we can use these interfaces to access settings from the configuration file.
 
Please note, the options class you are going to bind with your configuration settings:
  • Must be non-abstract.
  • Must have a public parameter-less constructor.
  • Each of its properties will be bound to the configuration, must have a public setter.
The main advantage of options pattern is that we create classes and each class which will have properties for configuring the specific parts of the application, which implements the Single Responsibility Principle. Let's move on the code part and see options pattern using IOptions<T> first in action.
 
Source Code
 
You can get the source code from GitHub.
 
Let's consider the same configurations we used in previous article:
  1. {  
  2. ....  
  3. },  
  4. "AllowedHosts""*",  
  5. "DashboardSettings": {  
  6.       "Title""C# Corner - A Community for Software Developers",  
  7.       "Header": {  
  8.       "IsSearchBoxEnabled"true,  
  9.       "BannerTitle""Breaking News",  
  10.       "IsBannerSliderEnabled"false  
  11.    }  
  12. }  
  13. }  

IOptions<TOptions>

The IOptions service is used to bind strongly types options class to configuration section and registers it to the Asp.Net Core Dependency Injection Service Container as singleton lifetime. It exposes a Value property which contains your configured TOptions class.
 
Now create a class called "DashboardHeaderConfiguration" under Models/Configuration folder with the properties which will bind to same name keys of the configuration section:
 
DashboardHeaderConfiguration.cs
  1. public class DashboardHeaderConfiguration  
  2. {  
  3.    public bool IsSearchBoxEnabled { getset; }  
  4.    public string BannerTitle { getset; }  
  5.    public bool IsBannerSliderEnabled { getset; }  
  6. }  
Now we will consume this options class to read settings inside our controller.
 
Dashboard2Controller.cs
  1. public class Dashboard2Controller : Controller  
  2. {  
  3.    private readonly DashboardHeaderConfiguration dashboardHeaderConfig;  
  4.   
  5.    //using Microsoft.Extensions.Options required  
  6.    public Dashboard2Controller(IOptions<DashboardHeaderConfiguration> options)  
  7.    {  
  8.       dashboardHeaderConfig = options.Value;  
  9.    }    
  10.    public IActionResult Index()  
  11.    {  
  12.       //dashboardHeaderConfig.BannerTitle //returns "Breaking News"  
  13.       //dashboardHeaderConfig.IsBannerSliderEnabled //returns false  
  14.       //dashboardHeaderConfig.IsSearchBoxEnabled //returns true  
  15.   
  16.       return Content(JsonConvert.SerializeObject(dashboardHeaderConfig));  
  17.    }  
  18. }  
In the above code, as you can see, we used the IOptions interface instead of the IConfiguration interface, which is inside Microsoft.Extensions.Options namespace. IOptions is a generic interface that requires TOptions type where we specify our options class "DashboardHeaderConfiguration" as options type. When the Dashboard2Controller is executed, the required IOptions instance will be injected from the asp.net dependency injection container. IOptions interface has a "Value" property through which we can access the instance of our options class which is "DashboardHeaderConfiguration" in our case. This way, we can access configuration settings mapped to this option's class instance properties.
 
As of now, we created an options class and consumed it in the controller, but still won't be able to bind configurations yet. To do this, we need to call the "Configure" method of IServiceCollection under "ConfigureServices" method in startup class and register a configuration instance which options class will bind against.
 
Startup.cs
  1. public class Startup  
  2. {  
  3.    private readonly IConfiguration _configuration;  
  4.   
  5.    public Startup(IConfiguration configuration)  
  6.    {  
  7.       this._configuration = configuration;  
  8.    }  
  9.   
  10.    public void ConfigureServices(IServiceCollection services)  
  11.    {  
  12.       services.AddControllersWithViews();  
  13.   
  14.       services.Configure<DashboardHeaderConfiguration>(this._configuration.GetSection("DashboardSettings:Header"));  
  15.    }  
  16.    ...  
  17.    ...  
  18. }  
This way, we can access configuration using options pattern and use them throughout the application.
 
Output
 
ASP.NET Core - Accessing Configurations Using Options Pattern 
So one important difference is that using Options pattern, we can access the options wherever configurations are needed through our dependency injection. And this can be very useful when the same configuration values need to be consumed across multiple parts of the application. Like simple binding using IConfiguration, we do not need to use hard-code keys for accessing configurations.
 

IOptionsSnapshot<TOptions>

 
IOptionsSnapshot service is another optional feature that is used to bind strongly types options class to configuration section and registers it to the Asp.Net Core Dependency Injection Service Container as scoped lifetime. It means it supports feature automatic reloading of configuration in the case that configuration settings are changed while the application is running.
 
Suppose we have some configuration settings in the appsettings.json file and the application is started. In case a few settings are changed while the application is running, IOptionsSnapshot provides us with a feature to automatically reload the changed configuration settings without restarting the application.
 
As the constructor dependency, we will use IOptionsSnapshot instead of IOptions interface.
 
Dashboard2Controller.cs
  1. public class Dashboard2Controller : Controller  
  2. {  
  3.    private readonly DashboardHeaderConfiguration dashboardHeaderConfig;  
  4.   
  5.    public Dashboard2Controller(IOptionsSnapshot<DashboardHeaderConfiguration> optionsSnapshot)  
  6.    {  
  7.       dashboardHeaderConfig = optionsSnapshot.Value;  
  8.    }    
  9.    public IActionResult Index()  
  10.    {  
  11.       return Content(JsonConvert.SerializeObject(dashboardHeaderConfig));  
  12.    }  
  13. }  
Output
 
Now as you will change configurations in appsettings.json file and save file while running application, you will get the latest updated values after refreshing the page.
Application need not to restart to get update about configuration settings. so IOptionsSnapshot<TOptions> is useful in those scenarios where options should be recomputed on every request.
 
ASP.NET Core - Accessing Configurations Using Options Pattern
 

IOptionsMonitor<TOptions>

 
IOptionsMonitor service is another option feature that is also used to bind strongly types options class to configuration section and registers it to the Asp.Net Core Dependency Injection Service Container as singleton lifetime like IOptions interface.
 
Why we need IOptionsMonitor when we have options as a singleton lifetime?
 
Before understanding why IOptionsMonitor required when we have IOptions service as a singleton lifetime registered, let's understand "What is Scoped Validation?". Since IOptionsSnapshot service provides us an automatic reloading feature and it is registered with the dependency injection container as a scope service, so we can not consume a scoped service from a singleton service. This is a safety feature of Asp.Net Dependency Injection Container called "Scoped Validation".
 
Since IOptions is registered as a single lifetime and it does not provide automatic reloading feature, so to get an automatic reloading feature like IOptionsSnapshot and to overcome "scoped validation" problem, we need IOptionsMonitor service.
 
Like IOptions, IOptionsMonitor is also registered with the dependency injection container as a singleton lifetime. This means that we are safe to inject it into other singleton services.
 
Let's see the first scoped validation issue and will see IOptionsMonitor in action.
 
Creating a singleton service
 
Add a folder "Services/Interfaces" and create a interface file called "IConfigurationReader" inside this folder and paste the following code:
  1. public interface IConfigurationReader  
  2. {  
  3.    string ReadDashboardHeaderSettings();  
  4. }  
Now add a folder "Services/Implementations" and create a class file called "ConfigurationReader" inside this folder and paste the following code:
  1. public class ConfigurationReader : IConfigurationReader  
  2. {  
  3.    private readonly DashboardHeaderConfiguration dashboardHeaderConfig;  
  4.   
  5.    public ConfigurationReader(IOptionsSnapshot<DashboardHeaderConfiguration> optionsSnapshot)  
  6.    {     
  7.    this.dashboardHeaderConfig = optionsSnapshot.Value;  
  8.    }  
  9.   
  10.    public string ReadDashboardHeaderSettings()  
  11.    {  
  12.    return JsonConvert.SerializeObject(this.dashboardHeaderConfig);  
  13.    }  
  14. }  
Now inject this service as a constructor dependency in controller and consume it:
  1. public class Dashboard3Controller : Controller  
  2. {  
  3.    private readonly IConfigurationReader configurationReader;  
  4.   
  5.    public Dashboard3Controller(IConfigurationReader configurationReader)  
  6.    {  
  7.       this.configurationReader = configurationReader;  
  8.    }  
  9.   
  10.    public IActionResult Index()  
  11.    {  
  12.       return Content(this.configurationReader.ReadDashboardHeaderSettings());  
  13.    }  
  14. }  
Before running the application, register a newly created service in dependency injection container as singleton lifetime under the ConfigureServices method in the startup.cs file:
  1. public void ConfigureServices(IServiceCollection services)    
  2. {    
  3.    services.AddControllersWithViews();    
  4.    services.Configure<DashboardHeaderConfiguration>(this._configuration.GetSection("DashboardSettings:Header"));    
  5.    services.AddSingleton<IConfigurationReader, ConfigurationReader>();    
  6. }    
Now run the application. You will get the below exception in the developer exception page, which clearly says " A scoped service can not be consumed from a singleton service"
 
 
 
 
 
 
 Solution
 
Since IOptionsMonitor is also registered with the dependency injection container as a singleton, this means that we are safe to inject it into our ConfigurationReader singleton service. IOptionsMonitor has a CurrentValue property rather tha Value property, which provides TOptions instance.
 
Update the following code in the ConfigurationReader.cs file,
  1. public class ConfigurationReader : IConfigurationReader    
  2. {    
  3.    private DashboardHeaderConfiguration dashboardHeaderConfig;    
  4.     
  5.    public ConfigurationReader(IOptionsMonitor<DashboardHeaderConfiguration> optionsMonitor)    
  6.    {    
  7.       this.dashboardHeaderConfig = optionsMonitor.CurrentValue;    
  8.       optionsMonitor.OnChange(config =>    
  9.       {    
  10.          this.dashboardHeaderConfig = config;    
  11.       });    
  12.    }    
  13.     
  14.    public string ReadDashboardHeaderSettings()    
  15.    {    
  16.       return JsonConvert.SerializeObject(this.dashboardHeaderConfig);    
  17.    }    
  18. }     
Output
 
Since you change configurations in appsettings.json file and save the file while running the application, you will get the latest updated values after refreshing the page.
 
 
 
 
 
IOptionsMonitor also provides an event called OnChange and a way to register a listener, that gets called whenever the TOptions instance is updated. This can be more useful when adding a variety of more advanced behaviors to an application. For example, if your application uses something like SignalR, you could have updated the behavior of UI of the application immediately after updating the configuration settings without refreshing the web page. This is probably the most beautiful feature that IOptionsMonitor provides. We will see it in detail later with another article where I will make an application to show you the real-time UI update without page refresh using this OnChange event of IOptionsMonitor.
 

Summary

 
Through this article, we learned a lot of features provided by Options Pattern:
  • How to use Options Pattern to access configurations from appsettings.json file in an ASP.NET project?
  • How to use IOptions<TOptions> for binding options classes?
  • How to use IOptionsSnapshot<TOptions> for binding options classes and how it provides an automatic configuration reloading feature?
  • What is Scoped Validation?
  • How to use IOptionsMonitor<TOptions> for binding options classes, consuming inside a singleton service and how it provides automatic configuration reloading feature?
 
Thanks a lot for reading. I hope you will love this article. Please share your valuable suggestions and feedbacks. Write in the comment box in case you have any questions. Have a good day!