In this post, we’ll see how to read the configuration file using the options pattern. The options pattern is recommended to read the app configuration in ASP.NET Core.
Before we see how to make configurations strongly typed the options pattern, we’ll see how to read the configuration settings using the IConfiguration
interface.
Reading Configuration settings with IConfiguration in ASP.NET Core
Let’s say we have the following configuration in our appsettings.json
file
{
"Pattern": {
"Name": "Options Pattern",
"Version": 1
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
And here’s how you read it
public class PrintOptionsController : ControllerBase
{
private IConfiguration _configuration;
public PrintOptionsController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public List<string> GetOptions()
{
var options = new List<string>();
options.Add(_configuration["Pattern:Name"]);
options.Add(_configuration["Pattern:Version"]);
options.Add(_configuration["Logging:LogLevel:Default"]);
return options;
}
}
To read nested options, we’ve to use a colon (:) as a separator between the nested properties. Ex: Pattern:Name
, Pattern:Version
If we run this, we should see this as the result
["Options Pattern","1","Information"]
This is working fine.
But, there’s a problem here. Every configuration value is a string no matter how you declare it in the appsettings.json
file.
And we’ve to hard code or store the configuration keys somewhere in the constants file so that we don’t have to hardcode it every time we use it.
This is not great because we have to manually parse the configuration values to the appropriate type every time.
So, the preferred way to read configuration is to use strongly typed classes with Options pattern.
What is the Options pattern?
The options pattern uses classes to provide strongly typed access to groups of related settings.
From Microsoft
The Options pattern adheres to the following software principles:
- Encapsulation or Interface Segregation Principle (ISP): Classes that depend on configuration settings rely only on the configuration settings that they use.
- _ Separation of Concerns _: As each class is tied to different settings in the configuration file they aren’t dependent or coupled.
Rules for Options class to work
Our options class should be
- Non-abstract and should have a public parameterless constructor
- All public read-write properties of the type are bound
Reading appsettings.json with Options pattern
The properties in our Options class should correspond to what we have in our appsettings.json
file. We had the following JSON in our appsettings.json
file which we wanted to be strongly typed.
"Pattern": {
"Name": "Options Pattern",
"Version": 1
}
So, our PatternOptions
class should have two properties. Name and Version.
public class PatternOptions
{
public const string SectionName = "Pattern";
public string Name { get; set; }
public int Version { get; set; }
}
We had the constant SectionName
, which is not bound to the configurations. This is just to avoid hardcoding while configuring options pattern in the startup class.
Now, let’s configure our PatternOptions
in the startup class.
In our ConfigureServices method in the startup class, add the following line
services.Configure<PatternOptions>(Configuration.GetSection(PatternOptions.SectionName));
That’s it! Our PatternOptions
class is now good to go. We can inject our class anywhere and read the configurations.
Injecting our PatternOptions class with IOptions interface
Instead of reading the configuration from IConfiguration
interface we will now read from IOptions<T>
. The T is our PatternOptions.
So, let’s remove the IConfiguration
injection from our PrintOptionsController
and inject IOptions
with PatternOptions
as a type.
public class PrintOptionsController : ControllerBase
{
private PatternOptions _patternOptions;
public PrintOptionsController(IOptions<PatternOptions> options)
{
_patternOptions = options?.Value;
}
[HttpGet("withOptionsPattern")]
public PatternOptions GetOptionsWithIOptions()
{
return _patternOptions;
}
}
Within the constructor, we have assigned options?.Value
to _patternOptions
variable. Or we could only get the .Value
only in the action method we need.
If we run the app, we should get our options from the configuration file.
{"name":"Options Pattern","version":5}
What are Named options?
Named options are used when we have multiple sections that bind to the same properties.
Let’s say we have the following in our appsettings.json
file
"Seasons": {
"Winter": {
"Month": "December",
"Temperature": 25
},
"Summer": {
"Month": "April",
"Temperature": 45
},
"Monsoon": {
"Month": "July",
"Temperature": 35
}
}
Now, if we’ve to make options pattern out of this, we’d have to create 3 different classes (1 for winter, 1 for summer, and the other for monsoon) and bind these in the section configuration in the startup class.
But, with Named options, we can have the following class and use it for all seasons.
public class SeasonsOptions
{
public const string Winter = "Winter";
public const string Summer = "Summer";
public const string Monsoon = "Monsoon";
public string Month { get; set; }
public int Temperature { get; set; }
}
Notice we have the common properties for all 3 seasons (Month and Temperature) and we have the constant variables for season names (these are not bound to the configuration as said earlier we’ll use them for configuration).
And in the startup, we can register the sections individually using the same SeasonsOptions class.
services.Configure<SeasonsOptions>(SeasonsOptions.Winter, Configuration.GetSection("Seasons:Winter"));
services.Configure<SeasonsOptions>(SeasonsOptions.Summer, Configuration.GetSection("Seasons:Summer"));
services.Configure<SeasonsOptions>(SeasonsOptions.Monsoon, Configuration.GetSection("Seasons:Monsoon"));
Once we are done with the setup we can modify our print options controller to be something like this
public class PrintOptionsController : ControllerBase
{
private SeasonsOptions _winterSeasonOptions;
private SeasonsOptions _summerSeasonOptions;
private SeasonsOptions _monsoonSeasonOptions;
public PrintOptionsController(IOptionsSnapshot<SeasonsOptions> namedWinterOptions)
{
_winterSeasonOptions = namedWinterOptions.Get(SeasonsOptions.Winter);
_summerSeasonOptions = namedWinterOptions.Get(SeasonsOptions.Summer);
_monsoonSeasonOptions = namedWinterOptions.Get(SeasonsOptions.Monsoon);
}
[HttpGet("getSeasonOptions")]
public List<SeasonsOptions> GetAllSeasons()
{
return new List<SeasonsOptions>
{
_winterSeasonOptions,
_summerSeasonOptions,
_monsoonSeasonOptions
};
}
}
Notice we are using IOptionsSnapshot
instead of IOptions
here. This is because the IOptions
interface does not support Named options.
IOptionsSnapshot
interface is generally used when options should be recomputed on every request. Yes, there’s a performance penalty with this as it is a scoped service and needs to be recomputed per request. So, use it with caution.
Now, if we run the app we should see all the seasons and their temperatures.
[{"month":"December","temperature":25},{"month":"April","temperature":45},{"month":"July","temperature":35}]
That’s it! Thanks for reading.
Please comment below and let me know your thoughts on the options pattern in ASP.NET Core.
References
The post Strongly Typed Configurations with Options Pattern appeared first on Code Rethinked.
Top comments (0)