The configuration API in ASP.NET core is now a first-class citizen. It is now easier than ever to configure applications within a .NET application. If you visit the main documentation page Configuration in ASP.NET Core it will give you detailed instructions on how to use it.
One part of the documentation discusses the IOptions snapshot. This allows you to take a configuration file - typically json - and automatically deserialise it into a plain old CLR object (POCO). It will also register this item against the built-in dependency injection system provided by .NET core:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
...
services.Configure<LoggingOptions>(this.Configuration.GetSection("Logging"));
...
public HomeController(IOptions<LoggingOptions> loggingOptions)
{
LoggingOptions = loggingOptions;
}
...
public IOptions<LoggingOptions> LoggingOptions { get; }
As the documentation suggests, every time you call Configure on the services, it adds a IConfigureOptions to the system for your POCO – where TOptions is the type of your POCO.
Sometimes, you want more control over how the particular options are configured. There is nothing stopping you from registering your own implementation of an IConfigureOptions class against the dependency injection engine as well.
Implementing your own IConfigureOptions class is powerful. It allows you to load from multiple sources, such as a database, or even a different web service somewhere. It also allows you to do pre-configuration work, such as adding defaults to options, and necessary computations to arrive at specific configuration values. Such an example is this:
public class LoggingOptionsSetup : IConfigureOptions<LoggingOptions>
{
public void Configure(LoggingOptions options)
{
// Perhaps some logic here that loads from a db, or some nice easy to use defaults.
options.IncludeScopes = true;
}
}
This example can then be registered with the dependency injection system:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<LoggingOptions>, LoggingOptionsSetup>());
You can see the ASP.NET team doing the exact same within their own MVC repository here.
Using the TryAddEnumerable setup will allow the ability for it to be built up as well by other calls in separate areas of the configuration setup. This can then be used in a similar way to the default configuration. You can pass an IOptions as a dependency, and the IConfigureOptions will initialise it for you.
I am personally not a fan of the IOptions as a dependency. I would much rather just have T as a dependency. One easy trick you can employ is to have custom initialisation for the class that depends on the configuration options:
services.Add(ServiceDescriptor.Singleton<HomeController>(s =>
{
var options = s.GetService<IOptions<LoggingOptions>>();
return new HomeController(options.Value);
}));
The class can then have no knowledge on how the configuration was instantiated, and I think I like this because it makes the use simpler for the consuming class. From what I can gather the use of the IOptions is really to make it all work nicely within the dependency injection engine, as it only has one property “Value” on it, and you are likely not to need the IOptions interface for anything else.
This first class way of passing configurations around is extremely powerful. IConfigureOptions is a feature that can make your configuration loading simpler, and give you simpler ways of separating concerns and organising your codebase.