microsoft-extensions-configuration について
保守しやすいアプリケーションを構築する .NET 開発者向けに、型付き設定、検証、オプションのライフタイムを解説するスキルです。
主な機能
- 01設定を型付きクラスにバインド
- 02起動時の設定検証
- 03独自オプション検証器の実装
- 04オプションのライフタイム選択
- 05バインド後のオプション設定
活用シーン
- 01アプリ起動前に無効な設定を検出
- 02複数プロパティにまたがる設定ルールの検証
- 03バックグラウンドサービスで再読み込み可能な設定を利用
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill microsoft-extensions-configurationインストール後、Claude Code で/microsoft-extensions-configurationを入力して読み込まれたか確認します。
同梱ファイルadvanced-patterns.md
詳細
Microsoft.Extensions Configuration Patterns
このスキルを使用する場面
次の場合にこのスキルを使用します:
- appsettings.json から構成を強く型付けされたクラスにバインドする場合
- アプリケーションの起動時に構成を検証する場合(フェイルファスト)
- 複雑な設定の検証ロジックを実装する場合
- テスト可能で保守しやすい構成クラスを設計する場合
- IOptions、IOptionsSnapshot、IOptionsMonitor を理解する場合
参照ファイル
- advanced-patterns.md:依存関係を持つバリデーター、名前付きオプション、完全な本番環境向けの例(AkkaSettings)、およびバリデーターのテスト
構成の検証が重要な理由
問題: 接続文字列の欠落、無効な URL、範囲外の値など、構成の誤りが原因でアプリケーションが実行時に失敗することはよくあります。こうした失敗は、構成を読み込む場所から離れたビジネスロジックの深い部分で発生します。
解決策: 起動時に構成を検証します。無効な場合は、明確なエラーメッセージとともに直ちに失敗させます。
// 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"
パターン 1:基本的なオプションのバインド
設定クラスを定義する
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;
}
}
パターン 2:Data Annotations による検証
単純な検証ルールには、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;
}
Data Annotations による検証を有効にする
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName)
.ValidateDataAnnotations() // Enable attribute-based validation
.ValidateOnStart(); // Validate immediately at startup
重要なポイント: .ValidateOnStart() は不可欠です。これを指定しない場合、検証はオプションが最初にアクセスされたときにのみ実行されます。
パターン 3:複雑な検証に IValidateOptions を使用する
Data Annotations は単純なルールには適していますが、複雑な検証には IValidateOptions<T> が必要です。
| シナリオ | Data Annotations | IValidateOptions |
|---|---|---|
| 必須フィールド | はい | はい |
| 範囲チェック | はい | はい |
| プロパティ間の検証 | いいえ | はい |
| 条件付き検証 | いいえ | はい |
| 外部サービスのチェック | いいえ | はい |
| バリデーターへの依存性注入 | いいえ | はい |
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 < 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>();
順序が重要です: まず Data Annotations が実行され、次に IValidateOptions バリデーターが実行されます。すべての失敗はまとめて収集されます。
依存関係を持つバリデーター、名前付きオプション、および完全な本番環境向けの例については、advanced-patterns.md を参照してください。
パターン 4:オプションの有効期間
| インターフェイス | 有効期間 | 変更時に再読み込み | 用途 |
|---|---|---|---|
IOptions<T> | シングルトン | いいえ | 静的な構成、一度だけ読み込む |
IOptionsSnapshot<T> | スコープ | はい(リクエストごと) | 新しい構成が必要な Web アプリ |
IOptionsMonitor<T> | シングルトン | はい(コールバック使用) | バックグラウンドサービス、リアルタイム更新 |
バックグラウンドサービスでの IOptionsMonitor
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);
}
}
}
パターン 5:構成後の処理
バインド後、検証の前にオプションを変更します。
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();
避けるべきアンチパターン
1. 構成への手動アクセス
// 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. コンストラクターでの検証
// 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. 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();
4. 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;
}
まとめ
| 原則 | 実装 |
|---|---|
| フェイルファスト | .ValidateOnStart() |
| 強く型付けする | POCO クラスにバインドする |
| 単純な検証 | Data Annotations |
| 複雑な検証 | IValidateOptions<T> |
| プロパティ間のルール | IValidateOptions<T> |
| 環境を考慮する | IHostEnvironment を注入する |
| テスト可能にする | バリデーターを通常のクラスにする |
コメント
Markdown 対応