Help us improve
Share bugs, ideas, or general feedback.
From dotnet-claude-kit
Provides .NET configuration patterns: Options pattern with validation, IOptions/IOptionsSnapshot/IOptionsMonitor injection, secrets management, environment variables. Use for appsettings, connection strings, config binding.
npx claudepluginhub codewithmukesh/dotnet-claude-kit --plugin dotnet-claude-kitHow this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-claude-kit:configurationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Options pattern always** — Never read `IConfiguration` directly in services. Bind configuration sections to strongly-typed classes with validation.
Configuration 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.
Implements the .NET Options pattern for strongly-typed configuration with IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T> including validation and reload support.
Provides .NET backend patterns for APIs, MCP servers, and enterprise apps: clean architecture, DI, EF Core, Dapper, Redis caching, IOptions config, and xUnit testing.
Share bugs, ideas, or general feedback.
IConfiguration directly in services. Bind configuration sections to strongly-typed classes with validation.ValidateDataAnnotations() and ValidateOnStart() to catch misconfiguration before the first request.appsettings.json → appsettings.{Environment}.json → environment variables → user secrets. Later sources override earlier ones.// Options class with validation attributes
public class DatabaseOptions
{
public const string SectionName = "Database";
[Required]
public required string ConnectionString { get; init; }
[Range(1, 100)]
public int MaxRetryCount { get; init; } = 3;
[Range(1, 60)]
public int CommandTimeoutSeconds { get; init; } = 30;
}
// Registration with validation
builder.Services.AddOptions<DatabaseOptions>()
.BindConfiguration(DatabaseOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart(); // Fails at startup if configuration is invalid
// appsettings.json
{
"Database": {
"ConnectionString": "",
"MaxRetryCount": 3,
"CommandTimeoutSeconds": 30
}
}
// IOptions<T> — singleton, read once at startup, doesn't change
public class OrderService(IOptions<DatabaseOptions> options)
{
private readonly DatabaseOptions _db = options.Value;
}
// IOptionsSnapshot<T> — scoped, re-reads per request (for reloadable config)
public class OrderService(IOptionsSnapshot<DatabaseOptions> options)
{
private readonly DatabaseOptions _db = options.Value;
}
// IOptionsMonitor<T> — singleton, actively watches for changes
public class BackgroundWorker(IOptionsMonitor<WorkerOptions> options)
{
public void DoWork()
{
var current = options.CurrentValue; // Always latest
}
}
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration("Jwt")
.Validate(options =>
{
if (string.IsNullOrEmpty(options.Key) || options.Key.Length < 32)
return false;
if (options.ExpirationMinutes <= 0)
return false;
return true;
}, "JWT key must be at least 32 characters and expiration must be positive")
.ValidateOnStart();
// Program.cs — add Key Vault as a configuration source
if (builder.Environment.IsProduction())
{
var keyVaultUri = new Uri(builder.Configuration["KeyVault:Uri"]!);
builder.Configuration.AddAzureKeyVault(keyVaultUri, new DefaultAzureCredential());
}
// Named options — different config per named instance
builder.Services.AddOptions<SmtpOptions>("internal")
.BindConfiguration("Smtp:Internal");
builder.Services.AddOptions<SmtpOptions>("customer")
.BindConfiguration("Smtp:Customer");
// Usage
public class EmailService(IOptionsSnapshot<SmtpOptions> options)
{
public async Task SendInternalEmail(string to, string body)
{
var smtp = options.Get("internal");
// ...
}
}
// BAD — stringly-typed, no validation, hard to test
public class OrderService(IConfiguration config)
{
public void Process()
{
var timeout = int.Parse(config["Database:CommandTimeout"]!);
}
}
// GOOD — strongly-typed options
public class OrderService(IOptions<DatabaseOptions> options)
{
public void Process()
{
var timeout = options.Value.CommandTimeoutSeconds;
}
}
// BAD — committed to source control
{
"Jwt": { "Key": "super-secret-key" },
"Database": { "ConnectionString": "Server=prod;Password=secret" }
}
// GOOD — appsettings.json has defaults/structure only
{
"Jwt": { "Key": "", "Issuer": "myapp", "Audience": "myapp" },
"Database": { "ConnectionString": "" }
}
// Secrets provided via user-secrets (dev) or env vars / Key Vault (prod)
// BAD — misconfiguration discovered at runtime
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
// GOOD — fail fast at startup
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration("Jwt")
.ValidateDataAnnotations()
.ValidateOnStart();
| Scenario | Recommendation |
|---|---|
| Binding config to class | Options pattern with BindConfiguration |
| Simple, immutable config | IOptions<T> |
| Config that changes per request | IOptionsSnapshot<T> |
| Background service watching config | IOptionsMonitor<T> |
| Development secrets | dotnet user-secrets |
| Production secrets | Azure Key Vault or environment variables |
| Validating config | ValidateDataAnnotations() + ValidateOnStart() |
| Multiple configs of same type | Named options with IOptionsSnapshot<T>.Get(name) |