Partilhar via


Implementar um provedor de configuração personalizado no .NET

Há muitos provedores de configuração disponíveis para fontes de configuração comuns, como arquivos JSON, XML e INI. Talvez seja necessário implementar um provedor de configuração personalizado quando um dos provedores disponíveis não atender às necessidades do seu aplicativo. Neste artigo, você aprenderá a implementar um provedor de configuração personalizado que depende de um banco de dados como sua fonte de configuração.

Provedor de configuração personalizada

O aplicativo de exemplo demonstra como criar um provedor de configuração básica que lê pares chave-valor de configuração de um banco de dados usando o Entity Framework (EF) Core.

O provedor tem as seguintes características:

  • O banco de dados EF in-memory é usado para fins de demonstração.
    • Para usar um banco de dados que exija uma cadeia de conexão, obtenha uma cadeia de conexão de uma configuração provisória.
  • O provedor lê uma tabela de banco de dados na configuração na inicialização. O provedor não consulta o banco de dados por chave.
  • A recarga na alteração não é implementada, portanto, atualizar o banco de dados após o início do aplicativo não afetará a configuração do aplicativo.

Defina uma entidade de Settings tipo de registro para armazenar valores de configuração no banco de dados. Por exemplo, você pode adicionar um arquivo Settings.cs na pasta Modelos :

namespace CustomProvider.Example.Models;

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

Para obter informações sobre tipos de registro, consulte Tipos de registro em C#.

Adicione um EntityConfigurationContext para armazenar e acessar os valores configurados.

Fornecedores/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")
        };
    }
}

Ao substituir, OnConfiguring(DbContextOptionsBuilder) você pode usar a conexão de banco de dados apropriada. Por exemplo, se uma cadeia de conexão fosse fornecida, você poderia se conectar ao SQL Server, caso contrário, poderia confiar em um banco de dados na memória.

Crie uma classe que implemente o IConfigurationSource.

Fornecedores/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);
}

Crie o provedor de configuração personalizada herdando do ConfigurationProvider. O provedor de configuração inicializa o banco de dados quando ele está vazio. Como as chaves de configuração não diferenciam maiúsculas de minúsculas, o dicionário usado para inicializar o banco de dados é criado com o comparador que não diferencia maiúsculas de minúsculas (StringComparer.OrdinalIgnoreCase).

Fornecedores/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;
    }
}

Um AddEntityConfiguration método de extensão permite adicionar a fonte de configuração à instância subjacente ConfigurationManager .

Extensões/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;
    }
}

Como o ConfigurationManager é uma implementação de e IConfigurationRoot, o método de extensão pode acessar a configuração de cadeias de IConfigurationBuilder conexão e adicionar o EntityConfigurationSource.

O código a seguir mostra como usar o personalizado EntityConfigurationProvider no Program.cs:

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.

Provedor de consumo

Para consumir o provedor de configuração personalizada, você pode usar o padrão de opções. Com o aplicativo de exemplo no lugar, defina um objeto de opções para representar as configurações do widget.

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!;
}

Uma chamada para Configure registrar uma instância de configuração, que TOptions se vincula contra.

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.

O código anterior configura o WidgetOptions objeto a "WidgetOptions" partir da seção da configuração. Isso habilita o padrão de opções, expondo uma representação pronta IOptions<WidgetOptions> para injeção de dependência das configurações do EF. As opções são, em última análise, fornecidas pelo provedor de configuração personalizada.

Consulte também