Implementowanie niestandardowego dostawcy konfiguracji na platformie .NET

Istnieje wiele dostawców konfiguracji dostępnych dla typowych źródeł konfiguracji, takich jak pliki JSON, XML i INI. Może być konieczne zaimplementowanie niestandardowego dostawcy konfiguracji, gdy jeden z dostępnych dostawców nie odpowiada potrzebom aplikacji. W tym artykule dowiesz się, jak zaimplementować niestandardowego dostawcę konfiguracji, który korzysta z bazy danych jako źródła konfiguracji.

Niestandardowy dostawca konfiguracji

Przykładowa aplikacja pokazuje, jak utworzyć podstawowego dostawcę konfiguracji, który odczytuje pary klucz-wartość konfiguracji z bazy danych przy użyciu programu Entity Framework (EF) Core.

Dostawca ma następujące charakterystyki:

  • Baza danych EF w pamięci jest używana podczas pokazów.
    • Aby użyć bazy danych, która wymaga parametry połączenia, pobierz parametry połączenia z konfiguracji tymczasowej.
  • Dostawca odczytuje tabelę bazy danych do konfiguracji w momencie uruchamiania. Dostawca nie wykonuje zapytań dotyczących bazy danych na podstawie poszczególnych kluczy.
  • Zmiana ponownego ładowania nie jest zaimplementowana, więc aktualizowanie bazy danych po uruchomieniu aplikacji nie wpłynie na konfigurację aplikacji.

Zdefiniuj jednostkę typu rekordu Settings do przechowywania wartości konfiguracji w bazie danych. Możesz na przykład dodać plik Ustawienia.cs w folderze Models:

namespace CustomProvider.Example.Models;

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

Aby uzyskać informacje na temat typów rekordów, zobacz Typy rekordów w języku C#.

Dodaj element EntityConfigurationContext służący do przechowywania skonfigurowanych wartości i uzyskiwania do nich dostępu.

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

Przez zastąpienie OnConfiguring(DbContextOptionsBuilder) można użyć odpowiedniego połączenia z bazą danych. Jeśli na przykład podano parametry połączenia, możesz nawiązać połączenie z programem SQL Server, w przeciwnym razie możesz polegać na bazie danych w pamięci.

Utwórz klasę, która implementuje IConfigurationSource.

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

Utwórz niestandardowego dostawcę konfiguracji, stosując dziedziczenie z ConfigurationProvider. Dostawca konfiguracji inicjuje bazę danych, gdy jest ona pusta. Ponieważ w kluczach konfiguracji nie jest uwzględniana wielkość liter, słownik używany do inicjowania bazy danych jest tworzony przy użyciu modułu porównywania bez uwzględniania wielkości liter (StringComparer.OrdinalIgnoreCase).

Dostawcy/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 Metoda rozszerzenia umożliwia dodanie źródła konfiguracji do wystąpienia bazowegoConfigurationManager.

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

ConfigurationManager Ponieważ parametr jest zarówno implementacją IConfigurationBuilder , jak i IConfigurationRoot, metoda rozszerzenia może uzyskać dostęp do konfiguracji parametry połączenia s i dodać element EntityConfigurationSource.

Poniższy kod pokazuje, jak używać niestandardowego EntityConfigurationProvider w 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.

Korzystanie z dostawcy

Aby korzystać z niestandardowego dostawcy konfiguracji, możesz użyć wzorca opcji. Po utworzeniu przykładowej aplikacji zdefiniuj obiekt options reprezentujący ustawienia widżetu.

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

Wywołanie w celu Configure zarejestrowania wystąpienia konfiguracji, które TOptions wiąże się z.

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.

Powyższy kod konfiguruje WidgetOptions obiekt z "WidgetOptions" sekcji konfiguracji. Umożliwia to wzorzec opcji, uwidaczniając reprezentację ustawień EF gotowej IOptions<WidgetOptions> do wstrzykiwania zależności. Opcje są ostatecznie udostępniane przez niestandardowego dostawcę konfiguracji.

Zobacz też