ASP.NET Coreアプリケーションの構成管理とオプションパターンの実装

ASP.NET Coreにおける構成管理の基礎

現実的なアプリケーションを開発する際、データベース接続文字列やAPIキー、ログ設定などのパラメータをソースコードから切り離して管理することは極めて重要です。これにより、デプロイ環境に応じて設定を変更する際にアプリケーションを再コンパイルする必要がなくなります。ASP.NET Coreは、このための柔軟で強力な構成システムを提供しています。

従来のASP.NETで使用されていたweb.configや静的なConfigurationManagerとは異なり、ASP.NET Coreは多様なソース(JSONファイル、環境変数、コマンドライン引数など)から設定を読み込み、それらを統合するモデルを採用しています。このシステムでは、後から読み込まれた設定値が以前の値を上書きするレイヤー構造を持っています。

本章では、ConfigurationBuilderを使用した基本的な構成の構築方法、機密情報の安全な管理、そして推奨されるオプションパターン(Options Pattern)を用いた強く型付けされた構成へのアクセス方法について詳しく解説します。

HostBuilderとデフォルトの構成

ASP.NET CoreアプリケーションのエントリポイントであるProgram.csでは、通常Host.CreateDefaultBuilderが使用されます。このヘルパーメソッドは、アプリケーション実行に必要なデフォルトの設定を自動的に行います。これには、ログ出力の設定や依存性注入(DI)コンテナの構成だけでなく、構成システムの初期化も含まれます。

CreateDefaultBuilder内部では、以下の順序で構成プロバイダーが自動的に追加されます。

  1. appsettings.json
  2. 環境変数(ASPNETCORE_ENVIRONMENTなど)に基づいたappsettings.{Environment}.json
  3. 開発環境でのユーザーシークレット(User Secrets)
  4. 環境変数
  5. コマンドライン引数

この順序が意味するのは、コマンドライン引数で指定された値が、JSONファイルや環境変数で指定された値をすべて上書きすることです。独自の構成ロジックを実装する場合、ConfigureAppConfigurationメソッドを使用してこの既定の動作をカスタマイズできます。

構成プロバイダーの実装とカスタマイズ

構成システムの中核を成すのはIConfigurationBuilderIConfigurationインターフェースです。ConfigureAppConfiguration内でIConfigurationBuilderに対して拡張メソッドを呼び出すことで、様々なソースから設定を読み込むプロバイダーを追加できます。

以下のコード例は、デフォルトのプロバイダーをクリアし、独自のJSONファイルと環境変数を読み込むようにカスタマイズした例です。

public static IHostBuilder CreateAppHost(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((context, builder) =>
        {
            // 既存のソースをクリアして完全にカスタマイズする場合
            builder.Sources.Clear();

            // ベースとなるJSON設定の読み込み
            builder.AddJsonFile("coreSettings.json", optional: false, reloadOnChange: true);

            // 環境ごとのオーバーライド設定の読み込み
            builder.AddJsonFile($"coreSettings.{context.HostingEnvironment.EnvironmentName}.json",
                               optional: true, reloadOnChange: true);

            // 環境変数の読み込み(他の設定を上書き可能)
            builder.AddEnvironmentVariables();
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

ここで重要なのは、プロバイダーを追加する順序です。上記の例では、JSONファイルの後に環境変数を追加しているため、環境変数にあるキーはJSONファイルの値を上書きします。

機密情報の管理(シークレット管理)

パスワードやAPIキーといった機密情報をコードリポジトリに含めることはセキュリティ上のリスクがあります。これらを安全に管理するために、ASP.NET Coreでは開発時にUser Secretsを、本番環境では環境変数Azure Key Vaultなどの安全なストアを使用する推奨されます。

開発環境においては、.NET CLIを使用してプロジェクトごとにシークレットを保存できます。これらはユーザープロファイルフォルダ内のJSONファイルに保存され、プロジェクトディレクトリ外にあるためリポジトリにコミットされる心配がありません。

# CLIを使用してシークレットを設定する例
dotnet user-secrets init
dotnet user-secrets set "ApiConfig:ServiceKey" "XyZ-123-ABCD"

本番環境では、OSやクラウドプラットフォームが提供する環境変数を使用するのが一般的です。構成システムは、環境変数の階層構造を表すためにコロン(:)の代わりに二重アンダースコア(__)もサポートしています。例えば、ApiConfig__ServiceKeyという環境変数は、ApiConfig:ServiceKeyという構成キーにマッピングされます。

オプションパターンによる強く型付けされた設定

IConfigurationインターフェースを直接使用して文字列キーで値を取得することも可能ですが、より堅牢な設計を行うにはオプションパターンを使用します。これにより、設定値をPOCO(Plain Old CLR Object)クラスにバインドし、型安全性とIntelliSenseの恩恵を受けることができます。

まず、設定を受け取るためのクラスを定義します。

public class GeoServiceOptions
{
    public const string SectionName = "GeoService";
    public int DefaultZoom { get; set; }
    public GeoPoint DefaultCenter { get; set; }
}

public class GeoPoint
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

次に、Startup.ConfigureServicesメソッドでConfigure<TOptions>拡張メソッドを使用して、構成のセクションをクラスにバインドし、DIコンテナに登録します。

public void ConfigureServices(IServiceCollection services)
{
    // appsettings.jsonの "GeoService" セクションを GeoServiceOptions クラスにバインド
    services.Configure<GeoServiceOptions>(Configuration.GetSection(GeoServiceOptions.SectionName));

    services.AddRazorPages();
}

サービスやコントローラーでは、IOptions<TOptions>インターフェースを通じてこれらの設定を注入します。

public class MapController : Controller
{
    private readonly GeoServiceOptions _settings;

    public MapController(IOptions<GeoServiceOptions> options)
    {
        // Valueプロパティ経由でバインドされたオブジェクトにアクセス
        _settings = options.Value;
    }

    public IActionResult Index()
    {
        // 型安全なプロパティアクセス
        var zoom = _settings.DefaultZoom;
        var lat = _settings.DefaultCenter.Latitude;
        return View();
    }
}

設定値の動的再読み込み(IOptionsSnapshot)

IOptions<T>はアプリケーション起動時に一度だけ読み込まれ、その後は変更されません。もしJSONファイルなどの変更を監視し、実行中に設定を更新したい場合は、IOptionsSnapshot<T>を使用します。

public class MapController : Controller
{
    private readonly GeoServiceOptions _settings;

    // IOptionsSnapshotを使用すると、リクエストごとに最新の設定が取得される
    public MapController(IOptionsSnapshot<GeoServiceOptions> options)
    {
        _settings = options.Value;
    }
}

IOptionsSnapshotはスコープ付きサービスとして機能するため、リクエストの lifetime 内で設定が再計算されます。これを機能させるには、構成プロバイダーの追加時にreloadOnChange: trueを設定している必要があります。

IOptionsインターフェースを使用しないバインディング

場合によっては、ラッパーであるIOptions<T>を介さず、直接POCOクラスをDIコンテナに登録したいこともあります。この場合、手動でバインドを行います。

public void ConfigureServices(IServiceCollection services)
{
    var opt = new GeoServiceOptions();
    Configuration.GetSection(GeoServiceOptions.SectionName).Bind(opt);
    
    // バインド済みのオブジェクトを直接シングルトンとして登録
    services.AddSingleton(opt);
}

この方法では動的な再読み込み機能は失われますが、依存クラスはラッパーなしで設定オブジェクトそのものを受け取れるため、ライブラリ開発などでは扱いやすくなります。

環境による設定の切り替え

ASP.NET Coreでは、IHostEnvironmentサービスを通じて現在の実行環境(Development, Staging, Productionなど)を特定できます。ASPNETCORE_ENVIRONMENT環境変数を設定することで、アプリケーションの挙動を環境ごとに切り替えることができます。

public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }
    // ... その他のミドルウェア
}

構成ファイルについても、環境に応じたファイルを読み込むのが一般的です。前述のConfigureAppConfigurationの例のように、appsettings.jsonの共通設定をベースとし、その後に環境固有のファイル(例: appsettings.Production.json)を読み込むことで、環境ごとの差分のみを記述できます。これにより、本番環境固有の接続文字列やAPIキーなどを安全に管理できます。

タグ: ASP.NET Core configuration Options Pattern IOptions Dependency Injection

6月28日 00:12 投稿