Share via


.NET でカスタム構成プロバイダーを実装する

JSON、XML、INI ファイルなどの一般的な構成ソースに使用できる構成プロバイダーは多数あります。 使用できるプロバイダーの 1 つがアプリケーションのニーズに合わない場合は、必要に応じてカスタム構成プロバイダーを実装します。 この記事では、構成ソースとしてデータベースに依存するカスタム構成プロバイダーを実装する方法について説明します。

カスタム構成プロバイダー

このサンプル アプリは、Entity Framework (EF) Core を使用してデータベースから構成のキーと値のペアを読み取る、基本的な構成プロバイダーを作成する方法を示します。

プロバイダーの特徴は次のとおりです。

  • EF のメモリ内データベースは、デモンストレーションのために使用されます。
    • 接続文字列を必要とするデータベースを使用するには、中間構成から接続文字列を取得します。
  • プロバイダーは、起動時に、構成にデータベース テーブルを読み取ります。 プロバイダーは、キー単位でデータベースを照会しません。
  • 変更時に再度読み込む機能は実装されていません。このため、アプリの起動後にデータベースを更新しても、アプリの構成には影響がありません。

データベースに構成値を格納するための Settings レコード型のエンティティを定義します。 たとえば、Models フォルダーに Settings.cs ファイルを追加できます。

namespace CustomProvider.Example.Models;

public record Settings(string Id, string? Value);

レコード型の詳細については、「C# のレコード型」を参照してください。

構成した値を格納し、その値にアクセスするための EntityConfigurationContext を追加します。

Providers/EntityConfigurationContext.cs:

using CustomProvider.Example.Models;
using Microsoft.EntityFrameworkCore;

namespace CustomProvider.Example.Providers;

public sealed class EntityConfigurationContext(string? connectionString) : DbContext
{
    public DbSet<Settings> Settings => Set<Settings>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        _ = connectionString switch
        {
            { Length: > 0 } => optionsBuilder.UseSqlServer(connectionString),
            _ => optionsBuilder.UseInMemoryDatabase("InMemoryDatabase")
        };
    }
}

OnConfiguring(DbContextOptionsBuilder) をオーバーライドすると、適切なデータベース接続を使用できます。 たとえば、接続文字列が指定されている場合は SQL Server に接続でき、そうでない場合は、メモリ内のデータベースに依存することができます。

IConfigurationSource を実装するクラスを作成します。

Providers/EntityConfigurationSource.cs:

using Microsoft.Extensions.Configuration;

namespace CustomProvider.Example.Providers;

public sealed class EntityConfigurationSource(
    string? connectionString) : IConfigurationSource
{
    public IConfigurationProvider Build(IConfigurationBuilder builder) =>
        new EntityConfigurationProvider(connectionString);
}

ConfigurationProvider から継承して、カスタム構成プロバイダーを作成します。 データベースが空だった場合、構成プロバイダーはこれを初期化します。 構成キーでは大文字と小文字が区別されないため、データベースの初期化に使用されるディクショナリは、大文字と小文字を区別しない比較子 (StringComparer.OrdinalIgnoreCase) を使用して作成されます。

Providers/EntityConfigurationProvider.cs:

using CustomProvider.Example.Models;
using Microsoft.Extensions.Configuration;

namespace CustomProvider.Example.Providers;

public sealed class EntityConfigurationProvider(
    string? connectionString)
    : ConfigurationProvider
{
    public override void Load()
    {
        using var dbContext = new EntityConfigurationContext(connectionString);

        dbContext.Database.EnsureCreated();

        Data = dbContext.Settings.Any()
            ? dbContext.Settings.ToDictionary(
                static c => c.Id,
                static c => c.Value, StringComparer.OrdinalIgnoreCase)
            : CreateAndSaveDefaultValues(dbContext);
    }

    static Dictionary<string, string?> CreateAndSaveDefaultValues(
        EntityConfigurationContext context)
    {
        var settings = new Dictionary<string, string?>(
            StringComparer.OrdinalIgnoreCase)
        {
            ["WidgetOptions:EndpointId"] = "b3da3c4c-9c4e-4411-bc4d-609e2dcc5c67",
            ["WidgetOptions:DisplayLabel"] = "Widgets Incorporated, LLC.",
            ["WidgetOptions:WidgetRoute"] = "api/widgets"
        };

        context.Settings.AddRange(
            [.. settings.Select(static kvp => new Settings(kvp.Key, kvp.Value))]);

        context.SaveChanges();

        return settings;
    }
}

AddEntityConfiguration 拡張メソッドを使用すると、基礎となる ConfigurationManager インスタンスに構成ソースを追加できます。

Extensions/ConfigurationManagerExtensions.cs:

using CustomProvider.Example.Providers;

namespace Microsoft.Extensions.Configuration;

public static class ConfigurationManagerExtensions
{
    public static ConfigurationManager AddEntityConfiguration(
        this ConfigurationManager manager)
    {
        var connectionString = manager.GetConnectionString("WidgetConnectionString");

        IConfigurationBuilder configBuilder = manager;
        configBuilder.Add(new EntityConfigurationSource(connectionString));

        return manager;
    }
}

ConfigurationManagerIConfigurationBuilderIConfigurationRoot の両方の実装であるため、拡張メソッドは接続文字列の構成にアクセスし、EntityConfigurationSource を追加できます。

次のコードでは、Program.cs でカスタムの EntityConfigurationProvider を使用する方法を示します。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using CustomProvider.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Configuration.AddEntityConfiguration();

builder.Services.Configure<WidgetOptions>(
    builder.Configuration.GetSection("WidgetOptions"));

using IHost host = builder.Build();

WidgetOptions options = host.Services.GetRequiredService<IOptions<WidgetOptions>>().Value;
Console.WriteLine($"DisplayLabel={options.DisplayLabel}");
Console.WriteLine($"EndpointId={options.EndpointId}");
Console.WriteLine($"WidgetRoute={options.WidgetRoute}");

await host.RunAsync();
// Sample output:
//    WidgetRoute=api/widgets
//    EndpointId=b3da3c4c-9c4e-4411-bc4d-609e2dcc5c67
//    DisplayLabel=Widgets Incorporated, LLC.

プロバイダーの使用

カスタム構成プロバイダーを使用するには、オプション パターンを使用できます。 サンプル アプリを所定の場所に配置し、ウィジェットの設定を表すオプション オブジェクトを定義します。

namespace CustomProvider.Example;

public class WidgetOptions
{
    public required Guid EndpointId { get; set; }

    public required string DisplayLabel { get; set; } = null!;

    public required string WidgetRoute { get; set; } = null!;
}

Configure を呼び出すと、TOptions がバインドする構成インスタンスが登録されます。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using CustomProvider.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Configuration.AddEntityConfiguration();

builder.Services.Configure<WidgetOptions>(
    builder.Configuration.GetSection("WidgetOptions"));

using IHost host = builder.Build();

WidgetOptions options = host.Services.GetRequiredService<IOptions<WidgetOptions>>().Value;
Console.WriteLine($"DisplayLabel={options.DisplayLabel}");
Console.WriteLine($"EndpointId={options.EndpointId}");
Console.WriteLine($"WidgetRoute={options.WidgetRoute}");

await host.RunAsync();
// Sample output:
//    WidgetRoute=api/widgets
//    EndpointId=b3da3c4c-9c4e-4411-bc4d-609e2dcc5c67
//    DisplayLabel=Widgets Incorporated, LLC.

上記のコードは、構成の "WidgetOptions" セクションから WidgetOptions オブジェクトを構成します。 これにより、オプション パターンが有効になり、EF 設定の依存関係の挿入対応の IOptions<WidgetOptions> 表現が公開されます。 これらのオプションは、最終的にカスタム構成プロバイダーから提供されます。

関連項目