C# Configuration & Options Pattern - Cheatsheet

2026/02/043 min read
bookmark this

C# Configuration & Options Pattern — Cheatsheet

A quick, scannable one-page reference for configuration sources, Options pattern registration, lifetime decisions, and common patterns in ASP.NET Core.

Configuration Sources (Last Wins)

1. appsettings.json
2. appsettings.{Environment}.json  (e.g., appsettings.Development.json)
3. User Secrets (Development only)
4. Environment Variables
5. Command-line arguments

Quick Start — Register & Consume

Register in Program.cs:

builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

Inject & use:

public class EmailSender
{
    private readonly EmailSettings _settings;
    public EmailSender(IOptions<EmailSettings> options) => _settings = options.Value;
    
    public void Send(string to) { /* use _settings */ }
}

Options Interfaces — Choose Your Lifetime

Interface Lifetime Reloads Use For
IOptions<T> Singleton No Static settings (secrets, connection strings)
IOptionsSnapshot<T> Scoped Per-request Changing settings in scoped services (feature flags)
IOptionsMonitor<T> Singleton Live + callback Singletons that react to config changes

Named Options — Multiple Configs of Same Type

// Register
builder.Services.Configure<BlobStorageSettings>("Images", 
    builder.Configuration.GetSection("Storage:Images"));
builder.Services.Configure<BlobStorageSettings>("Documents", 
    builder.Configuration.GetSection("Storage:Documents"));

// Consume
public class StorageService
{
    public StorageService(IOptionsSnapshot<BlobStorageSettings> options)
    {
        var imageContainer = options.Get("Images").ContainerName;
        var docContainer = options.Get("Documents").ContainerName;
    }
}

Validation — Always Use .ValidateOnStart()

builder.Services
    .AddOptions<EmailSettings>()
    .BindConfiguration("EmailSettings")
    .ValidateDataAnnotations()
    .ValidateOnStart();  // ← Catches errors at startup, not first request

With data annotations:

public class EmailSettings
{
    [Required]
    public string SmtpHost { get; init; } = string.Empty;
    
    [Range(1, 65535)]
    public int SmtpPort { get; init; } = 587;
    
    [EmailAddress]
    public string FromAddress { get; init; } = string.Empty;
}

Custom inline validation:

builder.Services
    .AddOptions<EmailSettings>()
    .BindConfiguration("EmailSettings")
    .Validate(s => !string.IsNullOrEmpty(s.SmtpHost) && s.SmtpPort is >= 1 and <= 65535, 
              "Invalid SMTP settings")
    .ValidateOnStart();

Environment Variables

Use double underscore (__) as section separator:

export EmailSettings__SmtpHost=smtp.example.com
export EmailSettings__SmtpPort=587
export ConnectionStrings__Default="Server=prod;Database=MyApp"

PostConfigure — Computed Defaults

builder.Services.PostConfigure<EmailSettings>(settings =>
{
    if (string.IsNullOrEmpty(settings.FromAddress))
        settings.FromAddress = $"noreply@{settings.SmtpHost}";
});

User Secrets (Development Only)

# Initialize
dotnet user-secrets init

# Set a secret
dotnet user-secrets set "EmailSettings:ApiKey" "my-secret-key"

# Access via IConfiguration (no code change needed)
var apiKey = configuration["EmailSettings:ApiKey"];

Binding Patterns

Method 1 — Simple (no validation):

builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));

Method 2 — With validation (recommended):

builder.Services
    .AddOptions<EmailSettings>()
    .BindConfiguration("EmailSettings")
    .ValidateDataAnnotations()
    .ValidateOnStart();

Method 3 — Immediate binding (no DI):

var settings = builder.Configuration.GetSection("EmailSettings").Get<EmailSettings>()!;

IOptionsMonitor with Change Callback

public class CacheService : IDisposable
{
    private CacheSettings _settings;
    private readonly IDisposable? _changeListener;

    public CacheService(IOptionsMonitor<CacheSettings> monitor)
    {
        _settings = monitor.CurrentValue;
        _changeListener = monitor.OnChange(newSettings =>
        {
            _settings = newSettings;
            Console.WriteLine($"Cache updated: TTL={_settings.TtlMinutes}m");
        });
    }

    public void Dispose() => _changeListener?.Dispose();
}

Anti-Patterns & Fixes

Problem Fix
IConfiguration everywhere (magic strings) Use IOptions<T> for type safety
Secrets in appsettings.json Use User Secrets (dev) or env vars (prod)
No validation on options Use ValidateDataAnnotations() + ValidateOnStart()
IOptions<T> in Singleton needing live reload Use IOptionsMonitor<T>
IOptionsSnapshot<T> in Singleton Runtime error — use IOptionsMonitor<T>

Quick Checklist

  • Config is in appsettings.json with environment overrides
  • Options class created with public const string SectionName
  • Registered in Program.cs with Configure<T>() or AddOptions<T>().BindConfiguration()
  • Validation configured with ValidateDataAnnotations() and ValidateOnStart()
  • Secrets stored in User Secrets (dev) or env vars (prod)
  • Correct interface chosen: IOptions<T>, IOptionsSnapshot<T>, or IOptionsMonitor<T>
  • PostConfigure<T>() used for computed defaults if needed

See the full guide: C# Configuration & Options Pattern