From dotnet-skills
Binds appsettings.json to strongly-typed .NET classes using Microsoft.Extensions.Options with startup validation and IValidateOptions for fail-fast config checks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-skills:microsoft-extensions-configurationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when:
Use this skill when:
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"
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;
}
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName);
// appsettings.json
{
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"Username": "[email protected]",
"Password": "secret",
"UseSsl": true
}
}
public class EmailService
{
private readonly SmtpSettings _settings;
// IOptions<T> - singleton, read once at startup
public EmailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
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;
}
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.
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 |
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 < 1 or > 65535)
failures.Add($"Port {options.Port} is invalid. Must be between 1 and 65535");
// Cross-property validation
if (!string.IsNullOrEmpty(options.Username) && string.IsNullOrEmpty(options.Password))
failures.Add("Password is required when Username is specified");
// Conditional validation
if (options.UseSsl && 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;
}
}
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.
| 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 |
public class BackgroundWorker : BackgroundService
{
private readonly IOptionsMonitor<WorkerSettings> _optionsMonitor;
private WorkerSettings _currentSettings;
public BackgroundWorker(IOptionsMonitor<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);
}
}
}
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();
// 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
}
}
// 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()
// 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();
// 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;
}
| 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 |
npx claudepluginhub aaronontheweb/dotnet-skills --plugin dotnet-skillsConfiguration patterns using Microsoft.Extensions.Configuration. Covers configuration providers, binding, validation, and best practices for .NET applications. Use when setting up configuration in .NET applications, implementing configuration validation with IValidateOptions, or managing settings across different environments.
Configuration patterns for .NET 10 applications: Options pattern, IOptionsSnapshot vs IOptions, secrets management, and environment-based configuration.
Implements the .NET Options pattern for strongly-typed configuration with IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T> including validation and reload support.