microsoft-extensions-configuration

Microsoft.Extensions Configuration Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "microsoft-extensions-configuration" with this command: npx skills add foxminchan/bookworm/foxminchan-bookworm-microsoft-extensions-configuration

Microsoft.Extensions Configuration Patterns

When to Use This Skill

Use this skill when:

  • Binding configuration from appsettings.json to strongly-typed classes

  • Validating configuration at application startup (fail fast)

  • Implementing complex validation logic for settings

  • Designing configuration classes that are testable and maintainable

  • Understanding IOptions, IOptionsSnapshot, and IOptionsMonitor

Reference Files

  • advanced-patterns.md: Validators with dependencies, named options, complete production example (AkkaSettings), and testing validators

Why Configuration Validation Matters

The Problem: Applications often fail at runtime due to misconfiguration - missing connection strings, invalid URLs, out-of-range values. These failures happen deep in business logic, far from where configuration is loaded.

The Solution: Validate configuration at startup. If invalid, fail immediately with a clear error message.

// BAD: Fails at runtime when someone tries to use the service public class EmailService { public EmailService(IOptions<SmtpSettings> options) { var settings = options.Value; // Throws NullReferenceException 10 minutes into production _client = new SmtpClient(settings.Host, settings.Port); } }

// GOOD: Fails at startup with clear error // "SmtpSettings validation failed: Host is required"

Pattern 1: Basic Options Binding

Define a Settings Class

public class SmtpSettings { public const string SectionName = "Smtp";

public string Host { get; set; } = string.Empty;
public int Port { get; set; } = 587;
public string? Username { get; set; }
public string? Password { get; set; }
public bool UseSsl { get; set; } = true;

}

Bind from Configuration

builder.Services.AddOptions<SmtpSettings>() .BindConfiguration(SmtpSettings.SectionName);

// appsettings.json { "Smtp": { "Host": "smtp.example.com", "Port": 587, "Username": "user@example.com", "Password": "secret", "UseSsl": true } }

Consume in Services

public class EmailService { private readonly SmtpSettings _settings;

// IOptions&#x3C;T> - singleton, read once at startup
public EmailService(IOptions&#x3C;SmtpSettings> options)
{
    _settings = options.Value;
}

}

Pattern 2: Data Annotations Validation

For simple validation rules, use Data Annotations:

using System.ComponentModel.DataAnnotations;

public class SmtpSettings { public const string SectionName = "Smtp";

[Required(ErrorMessage = "SMTP host is required")]
public string Host { get; set; } = string.Empty;

[Range(1, 65535, ErrorMessage = "Port must be between 1 and 65535")]
public int Port { get; set; } = 587;

[EmailAddress(ErrorMessage = "Username must be a valid email address")]
public string? Username { get; set; }

public string? Password { get; set; }
public bool UseSsl { get; set; } = true;

}

Enable Data Annotations Validation

builder.Services.AddOptions<SmtpSettings>() .BindConfiguration(SmtpSettings.SectionName) .ValidateDataAnnotations() // Enable attribute-based validation .ValidateOnStart(); // Validate immediately at startup

Key Point: .ValidateOnStart() is critical. Without it, validation only runs when the options are first accessed.

Pattern 3: IValidateOptions for Complex Validation

Data Annotations work for simple rules, but complex validation requires IValidateOptions<T> :

Scenario Data Annotations IValidateOptions

Required field Yes Yes

Range check Yes Yes

Cross-property validation No Yes

Conditional validation No Yes

External service checks No Yes

Dependency injection in validator No Yes

Implementing IValidateOptions

using Microsoft.Extensions.Options;

public class SmtpSettingsValidator : IValidateOptions<SmtpSettings> { public ValidateOptionsResult Validate(string? name, SmtpSettings options) { var failures = new List<string>();

    if (string.IsNullOrWhiteSpace(options.Host))
        failures.Add("Host is required");

    if (options.Port is &#x3C; 1 or > 65535)
        failures.Add($"Port {options.Port} is invalid. Must be between 1 and 65535");

    // Cross-property validation
    if (!string.IsNullOrEmpty(options.Username) &#x26;&#x26; string.IsNullOrEmpty(options.Password))
        failures.Add("Password is required when Username is specified");

    // Conditional validation
    if (options.UseSsl &#x26;&#x26; options.Port == 25)
        failures.Add("Port 25 is typically not used with SSL. Consider port 465 or 587");

    return failures.Count > 0
        ? ValidateOptionsResult.Fail(failures)
        : ValidateOptionsResult.Success;
}

}

Register the Validator

builder.Services.AddOptions<SmtpSettings>() .BindConfiguration(SmtpSettings.SectionName) .ValidateDataAnnotations() .ValidateOnStart();

builder.Services.AddSingleton<IValidateOptions<SmtpSettings>, SmtpSettingsValidator>();

Order matters: Data Annotations run first, then IValidateOptions validators. All failures are collected together.

See advanced-patterns.md for validators with dependencies, named options, and a complete production example.

Pattern 4: Options Lifetime

Interface Lifetime Reloads on Change Use Case

IOptions<T>

Singleton No Static config, read once

IOptionsSnapshot<T>

Scoped Yes (per request) Web apps needing fresh config

IOptionsMonitor<T>

Singleton Yes (with callback) Background services, real-time updates

IOptionsMonitor for Background Services

public class BackgroundWorker : BackgroundService { private readonly IOptionsMonitor<WorkerSettings> _optionsMonitor; private WorkerSettings _currentSettings;

public BackgroundWorker(IOptionsMonitor&#x3C;WorkerSettings> optionsMonitor)
{
    _optionsMonitor = optionsMonitor;
    _currentSettings = optionsMonitor.CurrentValue;

    _optionsMonitor.OnChange(settings =>
    {
        _currentSettings = settings;
    });
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        await DoWorkAsync();
        await Task.Delay(_currentSettings.PollingInterval, stoppingToken);
    }
}

}

Pattern 5: Post-Configuration

Modify options after binding but before validation:

builder.Services.AddOptions<ApiSettings>() .BindConfiguration("Api") .PostConfigure(options => { if (!string.IsNullOrEmpty(options.BaseUrl) && !options.BaseUrl.EndsWith('/')) options.BaseUrl += '/';

    options.Timeout ??= TimeSpan.FromSeconds(30);
})
.ValidateDataAnnotations()
.ValidateOnStart();

Anti-Patterns to Avoid

  1. Manual Configuration Access

// BAD: Bypasses validation, hard to test public class MyService { public MyService(IConfiguration configuration) { var host = configuration["Smtp:Host"]; // No validation! } }

// GOOD: Strongly-typed, validated public class MyService { public MyService(IOptions<SmtpSettings> options) { var host = options.Value.Host; // Validated at startup } }

  1. Validation in Constructor

// BAD: Validation happens at runtime, not startup public class MyService { public MyService(IOptions<Settings> options) { if (string.IsNullOrEmpty(options.Value.Required)) throw new ArgumentException("Required is missing"); // Too late! } }

// GOOD: Validation at startup via IValidateOptions + ValidateOnStart()

  1. Forgetting ValidateOnStart

// BAD: Validation only runs when first accessed builder.Services.AddOptions<Settings>() .ValidateDataAnnotations(); // Missing ValidateOnStart!

// GOOD: Fails immediately if invalid builder.Services.AddOptions<Settings>() .ValidateDataAnnotations() .ValidateOnStart();

  1. Throwing in IValidateOptions

// BAD: Throws exception, breaks validation chain public ValidateOptionsResult Validate(string? name, Settings options) { if (options.Value < 0) throw new ArgumentException("Value cannot be negative"); // Wrong! return ValidateOptionsResult.Success; }

// GOOD: Return failure result public ValidateOptionsResult Validate(string? name, Settings options) { if (options.Value < 0) return ValidateOptionsResult.Fail("Value cannot be negative"); return ValidateOptionsResult.Success; }

Summary

Principle Implementation

Fail fast .ValidateOnStart()

Strongly-typed Bind to POCO classes

Simple validation Data Annotations

Complex validation IValidateOptions<T>

Cross-property rules IValidateOptions<T>

Environment-aware Inject IHostEnvironment

Testable Validators are plain classes

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

csharp-type-design-performance

No summary provided by upstream source.

Repository SourceNeeds Review
General

microsoft-extensions-configuration

No summary provided by upstream source.

Repository SourceNeeds Review
General

microsoft-extensions-configuration

No summary provided by upstream source.

Repository SourceNeeds Review
General

image-gen

Generate AI images from text prompts. Triggers on: "生成图片", "画一张", "AI图", "generate image", "配图", "create picture", "draw", "visualize", "generate an image".

Archived SourceRecently Updated