Thursday, January 4, 2024

Reading Configuration using Different Options Patterns in ASP.NET Core

A few days ago I saw an interesting question where someone was asking what's the preferred way to read some configuration, something like below.
{
  "SomeSetting": {
    "Config""Value"
  }
}
Would you use, 

1. IConfiguration
app.MapGet("/config", (IConfiguration configuration) =>
{
    return configuration.GetValue<string>("SomeSetting:Config");
});
2. Options Pattern
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("SomeSetting"));

app.MapGet("/config", (IOptions<MyOptions> optionsAccessor) =>
{
    return optionsAccessor.Value.Config;
});

public record MyOptions
{
    public string Config { get; set; }
}
And that kind of prompted me to write this post.

As we all know, the more magic strings we maintain, the more trouble we ask for. But personally, if it's something that's hardly going to change, but still warrants being maintained as a configuration and does not use more than a couple of magic strings, I'd go for IConfiguration.

Saying that there are a lot of benefits to using Options Pattern. It uses strongly typed access and reduces the use of magic strings. We can also do Options Validation using Data Annotations, and we can even ask for early validation so that at the application start, we will know if something is missing or incorrectly configured rather than knowing it later when the app is. 

On top of those, there are different Options Interfaces that can be really handy.

  • It's a Singleton and CAN be injected into any service lifetime.
  • Supports Named Options.
  • Does not support reloadable changes. For new configuration values to be reflected, the app DOES need to be restarted.
  • Usage:
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("SomeSetting"));
..

app.MapGet("/config", (IOptions<MyOptions> optionsAccessor) =>
{
    return optionsAccessor.Value.Config;
});
  • It's Scoped and CAN NOT be injected into a Singleton service.
  • Supports Named Options.
  • Supports reloadable changes. For new configuration values to be reflected, the app DOES NOT need to be restarted. It's recomputed on every request and cached for the lifetime of the request.
  • Usage:
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("SomeSetting"));
..

app.MapGet("/config", (IOptionsSnapshot<MyOptions> snapshotOptionsAccessor) =>
{
    return snapshotOptionsAccessor.Value.Config;
});
  • It's a Singleton and CAN be injected into any service lifetime.
  • Supports Named Options.
  • Supports reloadable changes. For new configuration values to be reflected, the app DOES NOT need to be restarted. It's recomputed on every request and cached for the lifetime of the request.
  • Supports change notifications. That is if we want we can register a listener to be called whenever a named TOptions changes.
  • Usage:
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("SomeSetting"));
...

app.MapGet("/config", (IOptionsMonitor<MyOptions> optionsDelegate) =>
{
    return optionsDelegate.CurrentValue.Config;
});
Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment