Using Options Pattern in .NET Core for Strongly Typed Configuration

Search for a command to run...

No comments yet. Be the first to comment.
Hi! Usually, I listen to a song while writing something — but this time, I’m not listening to anything. Just chilling and letting my thoughts flow. Lately, I’ve stopped listening to music on the train, while doing the dishes, cleaning the house, or e...

Hello there! (Imagine I'm saying this in Obi-Wan Kenobi’s voice :D) I'm writing this newsletter while listening to a wonderful piece by Yann Tiersen. I'll tell you why I chose this song in the music section. Even if you haven't heard of Yann Tiersen,...

Note: This post was originally shared in my newsletter. If you enjoy it, don’t forget to subscribe for future letters! Hello there :D I've been chilling to "The Curse" by Agnes Obel while writing this, so expect a laid-back vibe throughout. A Cool Te...

Note: This post was originally shared in my newsletter. If you enjoy it, don’t forget to subscribe for future letters! I am writing this email while listening to Fade Into You by Mazzy Star. It’s a chill and relaxed song—if you’re in that mood, I hig...

Understanding an application's behavior in real-time is essential when deploying it. Think of it as checking the weather before heading out—you want to know the state of your app at a glance. Key indicators such as requests per minute (RPM), resource...

Hello everyone! In this tutorial, we are going to learn about the Options Pattern in ASP.NET Core, understand its purpose, and explore how to use it effectively.
If you want to learn via video, here is the Youtube video of this blog post:
Let's start by looking at a simple example. I have a GET endpoint named CityStatus, which returns the name and population of a city. The data for name and population is being read from the appsettings file under the key CityStatus.

Here's how the data in the appsettings file looks like:
{
"CityStatus": {
"Name": "Istanbul",
"Population": 20000
}
}
When I execute the endpoint, the response is the expected configuration data: Istanbul and 20000.
However, I don't have strong typing to the configuration keys in the controller's constructor. This is where the Options Pattern comes in.
First, let's create a class for our CityStatus configuration. I'll name this class CityStatusOptions and define two properties: Name and Population.
public class CityStatusOptions
{
public string Name { get; set; }
public int Population { get; set; }
}
Next, we need to bind our CityStatusOptions to the configuration. To do this, we'll use the Configure method in the Startup class.
services.Configure<CityStatusOptions>(Configuration.GetSection("CityStatus"));
Now, we can inject this strongly typed configuration using the IOptions<CityStatusOptions> interface in the controller.
public CityStatusController(IOptions<CityStatusOptions> options)
{
_options = options.Value;
}
We can now access the data in the endpoint using the _options.Name and _options.Population properties.

Executing the endpoint again, I get the same response as before, but now with a strongly typed configuration.
If we update the population value in the appsettings file and execute the endpoint again without restarting the application, we'll notice that the Options Pattern still returns the old value.
This is because the Options Pattern reads the data once and always returns the same value. To handle updated configuration values, we can use two interfaces: IOptionsSnapshot<T> and IOptionsMonitor<T>.
Here's how we can inject these interfaces and return the updated values in the endpoint:
public CityStatusController(IOptions<CityStatusOptions> options,
IOptionsSnapshot<CityStatusOptions> optionsSnapshot,
IOptionsMonitor<CityStatusOptions> optionsMonitor)
{
_options = options.Value;
_optionsSnapshot = optionsSnapshot.Value;
_optionsMonitor = optionsMonitor.CurrentValue;
}
With IOptionsSnapshot<T> and IOptionsMonitor<T>, we can now get the updated values in the response when the configuration is changed.
Although both interfaces provide updated values, there is a key difference between them: their lifecycles.
IOptionsMonitor<T> is a singleton and always returns the updated value, but it is always injected as a singleton.
IOptionsSnapshot<T> is scoped, and it reads the data from the configuration every time it is constructed.

Another feature of the Options Pattern is validating configurations using data annotations. For example, to ensure the Population value is not less than zero, we can use the [Range] attribute:
public class CityStatusOptions
{
public string Name { get; set; }
[Range(0, long.MaxValue)]
public int Population { get; set; }
}
To apply data validation, we need to bind the Options Pattern differently using the AddOptions method in the Startup class.
services.AddOptions<CityStatusOptions>()
.Bind(Configuration.GetSection("CityStatus"))
.ValidateDataAnnotations();
If we run the project and update the Population value to a negative number, an exception will be thrown when accessing the endpoint.
However, if we want the application to throw an exception during bootstrapping if the initial configuration is invalid, we need to use the ValidateOnStart method:
services.AddOptions<CityStatusOptions>()
.Bind(Configuration.GetSection("CityStatus"))
.ValidateDataAnnotations()
.ValidateOnStart();
Now, when running the project with an invalid initial configuration, the application throws an exception immediately.
In this tutorial, we learned about the Options Pattern in ASP.NET Core and its benefits for strongly typed configuration. We covered how to create a strongly typed class for configuration and how to handle configuration updates using IOptionsSnapshot<T> and IOptionsMonitor<T>, and how to validate configuration using data annotations.
Thank you for reading, and I hope this tutorial was helpful. Stay tuned for the next content; may the force be with you!