microsoft-extensions-configuration by aaronontheweb/dotnet-skills
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill microsoft-extensions-configuration在以下情况下使用此技能:
问题: 应用程序经常因配置错误而在运行时失败——缺少连接字符串、无效的 URL、超出范围的值。这些故障发生在业务逻辑深处,远离加载配置的地方。
解决方案: 在启动时验证配置。如果无效,立即失败并显示清晰的错误信息。
// 错误示例:当有人尝试使用服务时在运行时失败
public class EmailService
{
public EmailService(IOptions<SmtpSettings> options)
{
var settings = options.Value;
// 在生产环境运行 10 分钟后抛出 NullReferenceException
_client = new SmtpClient(settings.Host, settings.Port);
}
}
// 正确示例:在启动时失败并显示清晰错误
// "SmtpSettings 验证失败:Host 是必需的"
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;
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName);
// appsettings.json
{
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"Username": "user@example.com",
"Password": "secret",
"UseSsl": true
}
}
public class EmailService
{
private readonly SmtpSettings _settings;
// IOptions<T> - 单例,在启动时读取一次
public EmailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
对于简单的验证规则,使用数据注解:
using System.ComponentModel.DataAnnotations;
public class SmtpSettings
{
public const string SectionName = "Smtp";
[Required(ErrorMessage = "SMTP 主机是必需的")]
public string Host { get; set; } = string.Empty;
[Range(1, 65535, ErrorMessage = "端口必须在 1 到 65535 之间")]
public int Port { get; set; } = 587;
[EmailAddress(ErrorMessage = "用户名必须是有效的电子邮件地址")]
public string? Username { get; set; }
public string? Password { get; set; }
public bool UseSsl { get; set; } = true;
}
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName)
.ValidateDataAnnotations() // 启用基于属性的验证
.ValidateOnStart(); // 在启动时立即验证
关键点: .ValidateOnStart() 至关重要。没有它,验证只在选项首次被访问时运行。
数据注解适用于简单规则,但复杂验证需要 IValidateOptions<T>:
| 场景 | 数据注解 | 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 是必需的");
if (options.Port is < 1 or > 65535)
failures.Add($"端口 {options.Port} 无效。必须在 1 到 65535 之间");
// 跨属性验证
if (!string.IsNullOrEmpty(options.Username) && string.IsNullOrEmpty(options.Password))
failures.Add("指定用户名时必须提供密码");
// 条件验证
if (options.UseSsl && options.Port == 25)
failures.Add("端口 25 通常不与 SSL 一起使用。请考虑使用端口 465 或 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>();
顺序很重要: 数据注解首先运行,然后是 IValidateOptions 验证器。所有失败信息会一起收集。
有关包含依赖项的验证器、命名选项和完整生产示例,请参阅 advanced-patterns.md。
| 接口 | 生命周期 | 更改时重新加载 | 使用场景 |
|---|---|---|---|
IOptions<T> | 单例 | 否 | 静态配置,读取一次 |
IOptionsSnapshot<T> | 作用域 | 是(每个请求) | 需要新配置的 Web 应用 |
IOptionsMonitor<T> | 单例 | 是(带回调) | 后台服务,实时更新 |
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);
}
}
}
在绑定之后、验证之前修改选项:
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();
// 错误示例:绕过验证,难以测试
public class MyService
{
public MyService(IConfiguration configuration)
{
var host = configuration["Smtp:Host"]; // 没有验证!
}
}
// 正确示例:强类型,已验证
public class MyService
{
public MyService(IOptions<SmtpSettings> options)
{
var host = options.Value.Host; // 在启动时已验证
}
}
// 错误示例:验证发生在运行时,而不是启动时
public class MyService
{
public MyService(IOptions<Settings> options)
{
if (string.IsNullOrEmpty(options.Value.Required))
throw new ArgumentException("Required 缺失"); // 太晚了!
}
}
// 正确示例:通过 IValidateOptions + ValidateOnStart() 在启动时验证
// 错误示例:验证只在首次访问时运行
builder.Services.AddOptions<Settings>()
.ValidateDataAnnotations(); // 缺少 ValidateOnStart!
// 正确示例:如果无效则立即失败
builder.Services.AddOptions<Settings>()
.ValidateDataAnnotations()
.ValidateOnStart();
// 错误示例:抛出异常,破坏验证链
public ValidateOptionsResult Validate(string? name, Settings options)
{
if (options.Value < 0)
throw new ArgumentException("值不能为负数"); // 错误!
return ValidateOptionsResult.Success;
}
// 正确示例:返回失败结果
public ValidateOptionsResult Validate(string? name, Settings options)
{
if (options.Value < 0)
return ValidateOptionsResult.Fail("值不能为负数");
return ValidateOptionsResult.Success;
}
| 原则 | 实现 |
|---|---|
| 快速失败 | .ValidateOnStart() |
| 强类型 | 绑定到 POCO 类 |
| 简单验证 | 数据注解 |
| 复杂验证 | IValidateOptions<T> |
| 跨属性规则 | IValidateOptions<T> |
| 环境感知 | 注入 IHostEnvironment |
| 可测试 | 验证器是普通类 |
每周安装量
94
代码仓库
GitHub 星标数
491
首次出现时间
2026 年 1 月 28 日
安全审计
已安装于
claude-code72
codex60
github-copilot59
opencode59
gemini-cli56
kimi-cli53
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": "user@example.com",
"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 |
Weekly Installs
94
Repository
GitHub Stars
491
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code72
codex60
github-copilot59
opencode59
gemini-cli56
kimi-cli53
Go 错误处理最佳实践:提升代码可靠性与可维护性
674 周安装