patterncsharpdotnetTip
Options pattern: strongly typed configuration with IOptions<T> and validation
Viewed 0 times
.NET 6+
options patternIOptionsIOptionsSnapshotValidateOnStartstrongly typed configuration
Error Messages
Problem
Reading raw IConfiguration strings throughout the codebase is fragile — typos, missing keys, and wrong types fail at runtime rather than startup. There is no single place to validate configuration.
Solution
Bind configuration sections to typed classes and validate at startup:
For options that change at runtime use IOptionsSnapshot<T> (per-request) or IOptionsMonitor<T> (singleton with change notification).
// Options class
public class EmailOptions
{
public const string Section = "Email";
[Required] public string SmtpHost { get; set; } = string.Empty;
[Range(1, 65535)] public int Port { get; set; } = 587;
public bool UseSsl { get; set; } = true;
}
// Registration
builder.Services
.AddOptions<EmailOptions>()
.BindConfiguration(EmailOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart(); // fail fast at startup if invalid
// Consumption
public class EmailService(IOptions<EmailOptions> options)
{
private readonly EmailOptions _opts = options.Value;
}For options that change at runtime use IOptionsSnapshot<T> (per-request) or IOptionsMonitor<T> (singleton with change notification).
Why
IOptions<T>.Value is resolved once at first use and cached. IOptionsSnapshot<T> creates a new instance per scope (request) picking up config changes. IOptionsMonitor<T> watches the underlying source and fires OnChange callbacks.
Gotchas
- IOptions<T> captures the value at DI container build — config file changes are not reflected at runtime
- ValidateOnStart requires .NET 6+ — use IStartupFilter or custom IHostedService on older versions
- Nested options classes require explicit Bind() calls or use of the Binder source generator
Revisions (0)
No revisions yet.