.NET에서 사용자 지정 구성 공급자 구현

JSON, XML 및 INI 파일과 같은 일반적인 구성 소스에 사용할 수 있는 여러 구성 공급자가 있습니다. 사용 가능한 공급자 중 하나가 애플리케이션 요구 사항에 맞지 않는 경우 사용자 지정 구성 공급자를 구현해야 할 수 있습니다. 이 문서에서는 데이터베이스를 구성 소스로 사용하는 사용자 지정 구성 공급자를 구현하는 방법을 알아봅니다.

사용자 지정 구성 공급자

샘플 앱에서는 EF(Entity Framework) 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> 표현을 노출하는 옵션 패턴을 사용할 수 있습니다. 이 옵션은 궁극적으로 사용자 지정 구성 공급자에서 제공됩니다.

참고 항목