DOTNET Contents

Options Pattern & Config Binding

Use the Options pattern correctly: safe config binding, startup validation, reload behavior, and how to avoid silent misconfigurations that cause outages.

On this page

Why the Options Pattern Matters in Production

Configuration is production behavior. A single incorrect value can: - disable authentication - point to the wrong database - remove timeouts and cause cascading failures - enable debug logging and flood disks The Options pattern is how you bind configuration safely and predictably. If you misuse it, you ship silent misconfigurations that only explode after deployment.

Real Production Incident

Symptoms: - After deployment, outbound requests begin timing out. - Latency gradually increases. - Error rate spikes under moderate load. Root cause: - Timeout configuration existed but was not correctly bound. - Code fell back to a default (effectively infinite timeout). - Requests accumulated, threads blocked, and the service degraded. This was not a networking problem. It was a configuration binding failure.

Symptom → Cause → Diagnosis → Fix

Symptom: - Behavior differs between local and production. - Config appears defined but defaults are still used. Cause: - Wrong configuration section name - Incorrect class shape for binding - Missing validation - Using raw IConfiguration lookups - Injecting wrong Options interface type Diagnosis: - Log effective non-sensitive config values at startup. - Enable ValidateOnStart(). - Verify environment variable override behavior in the target environment. Fix: - Use strongly typed options. - Add validation rules. - Fail startup on invalid config.

Anti-Pattern: IConfiguration String Access

This approach is fragile and unvalidated:
var timeoutMs = int.Parse(builder.Configuration["Http:TimeoutMs"]);
services.AddSingleton(new SomeClient(timeoutMs));
Problems: - Missing keys cause runtime failures. - Typos silently break configuration. - No range enforcement. - No discoverability.

Correct Pattern: Strongly Typed Options

Define the options class:
public sealed class HttpClientOptions
{
    public int TimeoutMs { get; init; } = 5000;
    public int RetryCount { get; init; } = 2;
}
Bind and validate:
builder.Services
    .AddOptions()
    .Bind(builder.Configuration.GetSection("HttpClient"))
    .Validate(o => o.TimeoutMs >= 100 && o.TimeoutMs <= 60000, "TimeoutMs out of range")
    .Validate(o => o.RetryCount >= 0 && o.RetryCount <= 10, "RetryCount out of range")
    .ValidateOnStart();
Production rule: If the service cannot run safely, it should not start.

IOptions vs IOptionsSnapshot vs IOptionsMonitor

- IOptions: value fixed at startup. - IOptionsSnapshot: per-request snapshot (web apps only). - IOptionsMonitor: supports reload and change callbacks. Anti-pattern: Injecting IOptionsSnapshot into singleton services. Correct pattern: - Singleton services use IOptions or IOptionsMonitor. - Request-scoped behavior may use IOptionsSnapshot.

Environment Overrides

Environment variables override configuration files. Example: Section: HttpClient:TimeoutMs Environment variable: HttpClient__TimeoutMs Always test overrides in staging before production.

Security Notes

Options frequently contain secrets. Never: - Log full options objects - Log connection strings - Log API keys Log only non-sensitive derived values (timeouts, feature flags, limits).

Operational Notes

Monitoring: - Emit startup logs for critical non-secret settings. - Correlate incidents with config change timestamps. Rollout: - Canary deploy with strict validation enabled. - Fail fast if configuration is invalid. Rollback: - Prefer config rollback over code rollback when possible. - Keep config versioned and auditable.

Checklist

- Strongly typed options are used. - ValidateOnStart enabled for critical settings. - Environment variable override naming tested. - No raw IConfiguration string lookups in business logic. - No secrets logged. - Config changes are versioned and rollback-ready.