Spot on! You are completely right. I missed highlighting the Options pattern (IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>) in that section, which is the gold standard for reading configurations in controllers and services.

Let’s expand on why the Options pattern is critical in production-grade .NET applications and how it compares to raw IConfiguration injection.


The Options Pattern in ASP.NET Core

Instead of injecting raw IConfiguration everywhere and manually pulling strings like _configuration["ConnectionStrings:DefaultConnection"], the Options pattern binds configuration sections to strongly typed C# classes (POCOs).

1. Setup in Program.cs

Suppose your appsettings.json has a section like this:

{
  "JwtSettings": {
    "Secret": "super-secret-key-min-256-bits",
    "ExpiryMinutes": 60
  }
}
 

You create a matching C# class:

public class JwtSettings
{
    public string Secret { get; set; } = string.Empty;
    public int ExpiryMinutes { get; set; }
}
 

Then you bind it to the DI container in Program.cs:

// Binds the "JwtSettings" section from appsettings.json to the JwtSettings class
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
 

2. Consuming via Constructor Injection (IOptions<T> family)

Depending on your lifecycle and reloading needs, ASP.NET Core provides three different interfaces:

Option InterfaceBehavior & LifespanWhen to Use
IOptions<T>Singleton. Options are computed once at startup and never reloaded, even if appsettings.json changes on disk.For static configuration settings that never change during the app’s lifetime.
IOptionsSnapshot<T>Scoped. Re-computed on every new HTTP request. If configuration files change, the next request gets the updated values.Standard web controllers and services where hot-reloading configuration without restarting is desired.
IOptionsMonitor<T>Singleton. Supports change notifications. It provides a CurrentValue property and a OnChange(action) event listener.Background services, workers, or singletons that need to react immediately when configuration values change.

Example Usage in a Controller:

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly JwtSettings _jwtSettings;
 
    // Using IOptionsSnapshot for request-level configuration freshness
    public AuthController(IOptionsSnapshot<JwtSettings> jwtOptions)
    {
        _jwtSettings = jwtOptions.Value; // Access the bound strongly-typed class
    }
 
    [HttpGet("token-expiry")]
    public IActionResult GetExpiry()
    {
        return Ok(_jwtSettings.ExpiryMinutes);
    }
}
 

Why use the Options pattern instead of raw IConfiguration?

  1. Strong Typing: Eliminates magic strings scattered across your codebase (configuration["JwtSettings:Secret"]). Typo errors are caught at compile time or startup.
  2. Validation: You can easily add data annotations or fluent validation rules (e.g., ensuring ExpiryMinutes > 0) so your application fails fast at startup if configuration is missing or malformed.
  3. Separation of Concerns: Controllers and services don’t need to know where configuration comes from (JSON, environment variables, Azure Key Vault)—they just depend on a clean, strongly-typed configuration object.