microsoft-extensions-configuration · aaronontheweb — Claude Code skill: validate .NET
A skill for Claude and other AI editors that guides binding appsettings.json to strongly-typed classes and validating configuration at startup.
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a claude-codeWhat microsoft-extensions-configuration does
The skill covers Microsoft.Extensions.Options patterns for .NET: binding configuration sections to strongly-typed classes, validation with Data Annotations and IValidateOptions, and picking between IOptions<T>, IOptionsSnapshot<T> and IOptionsMonitor<T>. Useful when the agent writes service setup code and you would rather it did not scatter string keys across the project.
Inside are four patterns with C# examples: basic binding, attribute-based validation, a custom validator with dependency injection, and options lifetimes. One section explains why validation only fires on first access unless you add .ValidateOnStart(). A link to advanced-patterns.md covers named options and validators that need their own dependencies.
The skill is about code patterns, not package setup or a specific .NET version, so check that what the agent proposes matches your version of Microsoft.Extensions.Options. Order matters: Data Annotations run first, then IValidateOptions, and all failures are collected together.
What it helps with
- bind an appsettings.json section to a typed settings class
- validate configuration at startup and fail with a clear message
- write validation that compares several properties at once
- choose between IOptions, IOptionsSnapshot and IOptionsMonitor
- react to settings changes inside a background service
Example prompts for the agent
- Create an SmtpSettings class and bind it to the Smtp section in appsettings.json
- Add an IValidateOptions validator for these settings and turn on ValidateOnStart
- I need settings that reload at runtime — should I use IOptionsSnapshot or IOptionsMonitor?
How to install microsoft-extensions-configuration
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a claude-code2Manually: put the files in place
~/.claude/skills/microsoft-extensions-configuration/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/microsoft-extensions-configuration/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a codex2Manually: put the files in place
.agents/skills/microsoft-extensions-configuration/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a cursor2Manually: put the files in place
.cursor/skills/microsoft-extensions-configuration/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Aaronontheweb/dotnet-skills microsoft-extensions-configuration2Manually: put the files in place
.github/skills/microsoft-extensions-configuration/SKILL.md1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a gemini-cli2Platform CLI
gemini skills install https://github.com/Aaronontheweb/dotnet-skills --path skills/microsoft-extensions-configurationManually: put the files in place
.devin/skills/microsoft-extensions-configuration/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
From the same set32
Other versions of microsoft-extensions-configuration
Similar
Nearby in Backend and APIs
Everything for Backend and APIs →Questions about microsoft-extensions-configuration
How do I install microsoft-extensions-configuration in Claude?
Run npx skills add Aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration -a claude-code. The skill lands in ~/.claude/skills/microsoft-extensions-configuration/SKILL.md.
What does microsoft-extensions-configuration do?
It gives the agent Microsoft.Extensions.Options patterns: configuration binding, validation with Data Annotations and IValidateOptions, startup checks, and options lifetime selection.
How is IValidateOptions different from Data Annotations?
Attributes handle simple rules like a required field or a range. IValidateOptions is for checks that compare properties, depend on conditions, or need injected services. You can enable both at once.
Source text
SKILL.md · MIT
---
name: microsoft-extensions-configuration
description: Microsoft.Extensions.Options patterns including IValidateOptions, strongly-typed settings, validation on startup, and the Options pattern for clean configuration management.
user-invocable: false
---
# 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<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>
## Reference Files
- [advanced-patterns.md](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.
```csharp
// 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
```csharp
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
```csharp
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName);
// appsettings.json
{
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"Username": "[email protected]",
"Password": "secret",
"UseSsl": true
}
}
```
### Consume in Services
```csharp
public class EmailService
{
private readonly SmtpSettings _settings;
// IOptions<T> - singleton, read once at startup
public EmailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
```
---
## Pattern 2: Data Annotations Validation
For simple validation rules, use Data Annotations:
```csharp
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
```csharp
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<T> 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
```csharp
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;
}
}
```
### Register the Validator
```csharp
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](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
```csharp
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);
}
}
}
```
---
## Pattern 5: Post-Configuration
Modify options after binding but before validation:
```csharp
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
```csharp
// 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
}
}
```
### 2. Validation in Constructor
```csharp
// 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()
```
### 3. Forgetting ValidateOnStart
```csharp
// 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();
```
### 4. Throwing in IValidateOptions
```csharp
// 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 |
© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills