We want two different options to test out many possibilities:
- A default options (unnamed)
- A named instance.
To achieve this, we must add the following lines in the Program.cs file:
const string NamedInstance = “MyNamedInstance”;
builder.Services
.Configure<ConfigureMeOptions>(builder.Configuration
.GetSection(“configureMe”))
.Configure<ConfigureMeOptions>(NamedInstance, builder.Configuration
.GetSection(“configureMe”))
;
The preceding code registers a default instance (highlighted code) and a named instance. Both use the configureMe configuration sections and so start with the same initial values, as we can see when running the project:
{
“defaultInstance”: {
“title”: “Configure Me!”,
“lines”: [
“appsettings.json”
]
},
“namedInstance”: {
“title”: “Configure Me!”,
“lines”: [
“appsettings.json”
]
}
}
The defaultInstance and namedInstance properties are self-explanatory and relate to their respective options instance.Now that we completed our building blocks, we are ready to explore the IConfigureOptions<TOptions> interface.
Implementing a configurator object
We can encapsulate the configuration logic into classes to apply the single responsibility principle (SRP). To do so, we must implement an interface and create the binding with the IoC container.First, we must create a class that we name ConfigureAllConfigureMeOptions, which configures all ConfigureMeOptions instances; default and named:
namespace OptionsConfiguration;
public class ConfigureAllConfigureMeOptions : IConfigureNamedOptions<ConfigureMeOptions>
{
public void Configure(string?
name, ConfigureMeOptions options)
{
options.Lines = options.Lines.Append(
$”ConfigureAll:Configure name: {name}”);
if (name != Options.DefaultName)
{
options.Lines = options.Lines.Append(
$”ConfigureAll:Configure Not Default: {name}”);
}
}
public void Configure(ConfigureMeOptions options)
=> Configure(Options.DefaultName, options);
}
In the preceding code, we implement the interface (highlighted code), which contains two methods. The second Configure method should never be called, but just in case, we can simply redirect the call to the other method if it happens. The body of the first Configure method (highlighted) adds a line to all options and a second line when the options is not the default one.
Instead of testing if the options is not the default one (name != Options.DefaultName), you can check for the options name or use a switch to configure specific options by name.
We can tell the IoC container about this code, so ASP.NET Core executes it like this:
builder.Services.AddSingleton<IConfigureOptions<ConfigureMeOptions>, ConfigureAllConfigureMeOptions>();
Now with this binding in place, ASP.NET Core will run our code the first time we request our endpoint. Here’s the result:
{
“defaultInstance”: {
“title”: “Configure Me!”,
“lines”: [
“appsettings.json”,
“ConfigureAll:Configure name: “
]
},
“namedInstance”: {
“title”: “Configure Me!”,
“lines”: [
“appsettings.json”,
“ConfigureAll:Configure name: MyNamedInstance”,
“ConfigureAll:Configure Not Default: MyNamedInstance”
]
}
}
As we can see from that JSON output, the configurator ran and added the expected lines to each instance.
It is important to note that you must bind the IConfigureOptions<TOptions> to your configuration class even if you implemented the IConfigureNamedOptions<TOptions> interface.
And voilà—we have a neat result that took almost no effort. This can lead to so many possibilities! Implementing IConfigureOptions<TOptions> is probably the best way to configure the default values of an options class.Next, we add post-configuration to the mix!