Options pattern in ASP.NET Core: Advance scenario

Search for a command to run...

No comments yet. Be the first to comment.
In this series, I will write about advance concepts of ASP.NET Core. This includes following topics - DI - Middleware - Filters - Request processing pipeline - Logging, Exception Handling
Entity Framework Core (EF Core) is a powerful and flexible object-relational mapping (ORM) framework for .NET applications, enabling developers to work with relational databases using strongly-typed .NET objects. It simplifies data access by abstract...
ASP.NET Core Configuration Guide Overview ASP.NET Core uses a layered configuration system where later sources override earlier ones for the same key. Configuration Priority Order Priority Source
Legacy Delphi applications often struggle with outdated UI/UX, performance issues, and monolithic architectures that make refactoring difficult. However, a full rewrite is not always feasible due to time, cost, and the risk of disrupting critical bus...
Entity Framework Core (EF Core) is a powerful and flexible object-relational mapping (ORM) framework for .NET applications, enabling developers to work with relational databases using strongly-typed .NET objects. It simplifies data access by abstract...

In this article we will learn about Options pattern in ASP.NET Core for dealing with application configurations. The Options pattern uses the C# classes to access and manage the configurations thus providing more flexibility and type safe approach to...

In this article we are going to cover some advance scenarios where Options pattern is very useful.
We have covered the basic understanding of Options pattern into Options pattern in ASP.NET Core: Introduction article. Please go through it if not familiar with it.
We will cover below scenarios which are handled using Options pattern:
Validation of configuration settings using data annotation.
Validation of configuration settings using custom validation logic.
Validation of configuration settings during application startup.
Handling command line arguments or environment variables using options pattern.
Monitoring the changes of configuration settings.
Let's dive directly into code to understand all how Options pattern is used to handle all these scenarios.
GitHub repo link for the code sample https://github.com/dkj468/OptionsPatternDemo
Options pattern allows to bind the DataAnnotations rules to class which represents the section of application settings. This is powerful mechanism to validate for non empty, range and many more things.
consider the FileOptions class from our first article :
public class FileOptions {
public const string Key = "file";
[Required(AllowEmptyStrings =false)]
public string MaxSize { get; set; }
public string FileType { get; set; }
public bool CanModify { get; set; }
[Range(minimum:2, maximum:6)]
public int MaxFileCount { get; set; }
}
We have applied some validation rules to class properties which represents the configuration setting.
Now we need to modify Program.cs file to validate these data annotations.
builder.Services
.AddOptions<FileOptions>().Bind(builder.Configuration.GetSection("file"))
.ValidateDataAnnotations(); // validate the annotations
With above code changes, application is ready to validate annotation rule when user tries to access the configuration settings via Options pattern.
if you just remove the MaxSize property from appsettings.json file and try to access this inside controller then Microsoft.Extensions.Options.OptionsValidationException exception will be thrown with proper message.
Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed for 'FileOptions' members: 'MaxSize' with the error: 'The MaxSize field is required.'.
[Required(AllowEmptyStrings =false, ErrorMessage ="MaxSize cannot be empty")]
public string MaxSize { get; set; }
You could write custom validation logic to validate configuration settings value.
builder.Services
.AddOptions<FileOptions>().Bind(builder.Configuration.GetSection("file"))
.Validate(fileOptions => {
if (string.IsNullOrEmpty(fileOptions.FileType) {
return false;
}
return true;
});
builder.Services
.AddOptions<FileOptions>().Bind(builder.Configuration.GetSection("file"))
.ValidateDataAnnotations()
.ValidateOnStart();
With .NET 8 , you could combine this with AddOptions method.
builder.Services
.AddOptionsWithValidateOnStart<FileOptions>()
.Bind(builder.Configuration.GetSection("file"));
To manage command line arguments or environment variables using options pattern, we somehow need a way to use ASP.NET Core dependency injection mechanism to access the required data.
So far we have used AddOptions method to tell ASP.NET Core to bind the C# class with a specific section of the appsettings.json file. So this method only allows us to bind the configurations which are part of appsettings.json file. We need a way to bind the configuration from other sources like command line, environment variables, database, xml or ini files etc.
To achieve this, we will set options pattern using IConfigurationOptions . This is two step process:
Create a class which implements IConfigureOptions interface, this class will allow us to access the other configuration sources via dependency injection.
Modify Program.cs to use the class created in above step.
let's see this with code:
First, we will modify the FileOptions class to include two new properties: FileMode and Version.
let's create class with implementation of IConfigureOptions
public class FileOptionsSetup : IConfigureOptions<FileOptions> {
private readonly IConfiguration _config;
public FileOptionsSetup(IConfiguration config) {
_config = config;
}
public void Configure(FileOptions options) {
_config.GetSection("file").Bind(options);
// bind the environment variable
options.filemode = Environment.GetEnvironmentVariable("filemode");
// bind the command line argument
options.version = _config.GetValue<string>("version");
}
}
In above code, we first bind the appsettings.json section to FileOptions object and then read the configuration from environment variable and command line respectively.
We could extend above code to include configurations from almost any source
Now let's tell our program.cs to use above class for configurations.
builder.Services.ConfigureOptions<FileOptionsSetup>();
With all above code, our code is now powered to handle configuration from sources other than appsettings.json
Let's recall how our code uses the IOptions interface for injecting the FileOptions class into controller constructor. There are two more interfaces which could be used to inject the options classes vai dependency injection into ASP.NET Core application.
Let's understand more about these in details :
IOptions
After running the program, IOptions doesn't monitor for changes into application configurations. It always gives the same value.
This works as singleton and can be injected in any service lifetime.
IOptionsSnapshot
This is useful in scenario where configuration needs to be read for each request - It works as a scoped service.
This can be injected into a scoped or transient service but not in singleton service.
appsettings.json is read for each request.
IOptionsMonitor
This works as singleton and can be injected in any service lifetime.
this interface has an OnChnage method which is triggered if there are changes in appsettings.json
public FileController(IConfiguration config,
IOptionsMonitor<FileOptions> optionsMonitor) {
_config = config;
_optionsMonitor = optionsMonitor;
_fileOptions = _optionsMonitor.CurrentValue; // notice here
_optionsMonitor.OnChange(options => { // onchange method
_fileOptions = options;
});
}
🚀 Use IOptions<T> when you are not expecting changes in appsettings content.
🚀 Use IOptionsSnapshot<T> when values are expected to change but want it to be consistent during a request.
🚀 Use IOptionsMonitor<T> when real time value is expected.
In this article we have learned about some advance use cases where options pattern is quite useful. All such scenarios are difficult to manage using IConfiguration method.
Thanks for reading.