Key Architectural Principles
- 01.Never inject raw IConfiguration into business logic or domain services.
- 02.Use IOptions<T> for immutable singleton settings and IOptionsSnapshot<T> for per-request reloading.
- 03.Chain .ValidateDataAnnotations().ValidateOnStart() to fail immediately at startup upon invalid configurations.
- 04.Encapsulate third-party credentials and connection endpoints inside dedicated POCO options classes.
Relying on raw strings from IConfiguration across an enterprise application is an anti-pattern that leads to late-discovered runtime failures. The Options Pattern provides strongly-typed access to related settings groups while enforcing encapsulation.
In this guide, we walk through setting up IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>, while using DataAnnotations and ValidateOnStart() to ensure invalid configurations prevent faulty service startup.
We also look at sample implementations from enterprise repositories, including MailKit SMTP abstractions and JWT bearer security tokens.
1. The Hazards of Raw IConfiguration
Passing IConfiguration throughout your services violates Separation of Concerns and hides dependencies. Classes become tightly coupled to configuration structure and keys, making unit testing clumsy and refactoring perilous.
Strongly typed options POCOs isolate the configuration section into clean, testable C# records or classes that can be injected via standard dependency injection.
2. Lifecycle Differentiation: IOptions vs Snapshot vs Monitor
IOptions<T> is registered as a Singleton and calculates values once on startup. It offers the highest performance but cannot detect runtime configuration reloads.
IOptionsSnapshot<T> is Scoped and re-evaluates configuration per HTTP request, perfect for scenarios where appsettings or Key Vault configs reload dynamically.
IOptionsMonitor<T> is a Singleton that fires onChange events whenever configuration alters, essential for background workers and long-lived message processors.
3. Fail-Fast Startup Validation
Using .ValidateOnStart() introduced in .NET 6/7/8 ensures that if an engineer forgets to supply an environment variable or connection string in Kubernetes or Azure App Service, the container crashes during startup rather than crashing during a critical customer request.
// ASP.NET Core Startup Validation with DataAnnotations & ValidateOnStart
public static IServiceCollection AddMessagingConfiguration(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddOptions<ServiceBusOptions>()
.Bind(configuration.GetSection(ServiceBusOptions.SectionName))
.ValidateDataAnnotations()
.Validate(options =>
!string.IsNullOrWhiteSpace(options.ConnectionString) || !string.IsNullOrWhiteSpace(options.FullyQualifiedNamespace),
"Either ConnectionString or FullyQualifiedNamespace must be configured.")
.ValidateOnStart(); // Guarantees fail-fast on startup, not at first request!
return services;
}