Implementera en anpassad konfigurationsprovider i .NET

Det finns många konfigurationsprovidrar tillgängliga för vanliga konfigurationskällor, till exempel JSON-, XML- och INI-filer. Du kan behöva implementera en anpassad konfigurationsprovider när någon av de tillgängliga leverantörerna inte passar dina programbehov. I den här artikeln får du lära dig hur du implementerar en anpassad konfigurationsprovider som förlitar sig på en databas som dess konfigurationskälla.

Anpassad konfigurationsprovider

Exempelappen visar hur du skapar en grundläggande konfigurationsprovider som läser konfigurationsnyckel/värde-par från en databas med hjälp av Entity Framework (EF) Core.

Providern har följande egenskaper:

  • Databasen ef in-memory används i demonstrationssyfte.
    • Om du vill använda en databas som kräver en anslutningssträng hämtar du en anslutningssträng från en interimskonfiguration.
  • Providern läser en databastabell i konfigurationen vid start. Providern frågar inte databasen per nyckel.
  • Om du läser in på nytt implementeras inte, så att uppdatera databasen när appen har startats påverkar inte appens konfiguration.

Definiera en posttypsentitet Settings för lagring av konfigurationsvärden i databasen. Du kan till exempel lägga till en Inställningar.cs fil i mappen Modeller:

namespace CustomProvider.Example.Models;

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

Information om posttyper finns i Posttyper i C#.

Lägg till en EntityConfigurationContext för att lagra och komma åt de konfigurerade värdena.

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

Genom att OnConfiguring(DbContextOptionsBuilder) åsidosätta kan du använda lämplig databasanslutning. Om till exempel en anslutningssträng angavs kunde du ansluta till SQL Server, annars kan du förlita dig på en minnesintern databas.

Skapa en klass som implementerar IConfigurationSource.

Leverantörer/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);
}

Skapa den anpassade konfigurationsprovidern genom att ärva från ConfigurationProvider. Konfigurationsprovidern initierar databasen när den är tom. Eftersom konfigurationsnycklar är skiftlägeskänsliga skapas ordlistan som används för att initiera databasen med den skiftlägeskänsliga jämförelsen (StringComparer.OrdinalIgnoreCase).

Leverantörer/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;
    }
}

Med en AddEntityConfiguration tilläggsmetod kan du lägga till konfigurationskällan i den underliggande ConfigurationManager instansen.

Tillägg/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 Eftersom är både en implementering av IConfigurationBuilder och IConfigurationRootkan tilläggsmetoden komma åt konfigurationen anslutningssträng s och lägga till EntityConfigurationSource.

Följande kod visar hur du använder det anpassade EntityConfigurationProvider i 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.

Förbruka provider

Om du vill använda den anpassade konfigurationsprovidern kan du använda alternativmönstret. När exempelappen är på plats definierar du ett alternativobjekt som representerar widgetinställningarna.

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

Ett anrop till Configure registrerar en konfigurationsinstans som TOptions binder mot.

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.

Föregående kod konfigurerar WidgetOptions objektet från "WidgetOptions" avsnittet i konfigurationen. Detta aktiverar alternativmönstret och exponerar en beroendeinmatningsklar IOptions<WidgetOptions> representation av EF-inställningarna. Alternativen tillhandahålls i slutändan från den anpassade konfigurationsprovidern.

Se även