mcpField
M
microsoft-extensions-configuration

microsoft-extensions-configuration

github.com/aaronontheweb開発1.2k589 回インストール今日更新
リポジトリ

microsoft-extensions-configuration について

保守しやすいアプリケーションを構築する .NET 開発者向けに、型付き設定、検証、オプションのライフタイムを解説するスキルです。

主な機能

  1. 01設定を型付きクラスにバインド
  2. 02起動時の設定検証
  3. 03独自オプション検証器の実装
  4. 04オプションのライフタイム選択
  5. 05バインド後のオプション設定

活用シーン

  1. 01アプリ起動前に無効な設定を検出
  2. 02複数プロパティにまたがる設定ルールの検証
  3. 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 AnnotationsIValidateOptions
必須フィールドはいはい
範囲チェックはいはい
プロパティ間の検証いいえはい
条件付き検証いいえはい
外部サービスのチェックいいえはい
バリデーターへの依存性注入いいえはい
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 を注入する
テスト可能にするバリデーターを通常のクラスにする

コメント