Een aangepaste configuratieprovider implementeren in .NET

Er zijn veel configuratieproviders beschikbaar voor algemene configuratiebronnen, zoals JSON-, XML- en INI-bestanden. Mogelijk moet u een aangepaste configuratieprovider implementeren wanneer een van de beschikbare providers niet aan uw toepassingsbehoeften voldoet. In dit artikel leert u hoe u een aangepaste configuratieprovider implementeert die afhankelijk is van een database als configuratiebron.

Aangepaste configuratieprovider

De voorbeeld-app laat zien hoe u een eenvoudige configuratieprovider maakt die configuratiesleutel-waardeparen van een database leest met behulp van Entity Framework (EF) Core.

De provider heeft de volgende kenmerken:

  • De EF-database in het geheugen wordt gebruikt voor demonstratiedoeleinden.
    • Als u een database wilt gebruiken waarvoor een verbindingsreeks is vereist, haalt u een verbindingsreeks op uit een tussentijdse configuratie.
  • De provider leest een databasetabel in de configuratie bij het opstarten. De provider doorzoekt de database niet per sleutel.
  • Herload-on-change is niet geïmplementeerd, dus het bijwerken van de database nadat de app is gestart, heeft geen invloed op de configuratie van de app.

Definieer een Settings recordtypeentiteit voor het opslaan van configuratiewaarden in de database. U kunt bijvoorbeeld een Instellingen.cs-bestand toevoegen in de map Modellen:

namespace CustomProvider.Example.Models;

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

Zie Recordtypen in C# voor informatie over recordtypen.

Voeg een EntityConfigurationContext toe om de geconfigureerde waarden op te slaan en te openen.

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

Door te overschrijven OnConfiguring(DbContextOptionsBuilder) kunt u de juiste databaseverbinding gebruiken. Als er bijvoorbeeld een verbindingsreeks is opgegeven, kunt u verbinding maken met SQL Server, anders kunt u vertrouwen op een in-memory database.

Maak een klasse die wordt geïmplementeerd 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);
}

Maak de aangepaste configuratieprovider door deze over te nemen van ConfigurationProvider. De configuratieprovider initialiseert de database wanneer deze leeg is. Omdat configuratiesleutels hoofdlettergevoelig zijn, wordt de woordenlijst die wordt gebruikt om de database te initialiseren gemaakt met de hoofdlettergevoelige vergelijking (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;
    }
}

Met een AddEntityConfiguration extensiemethode kan de configuratiebron worden toegevoegd aan het onderliggende ConfigurationManager exemplaar.

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

Omdat het ConfigurationManager zowel een implementatie is van IConfigurationBuilder alsIConfigurationRoot, heeft de extensiemethode toegang tot de configuratie van verbindingsreeks s en voegt u de EntityConfigurationSource.

De volgende code laat zien hoe u de aangepaste EntityConfigurationProvider code gebruikt in 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.

Provider gebruiken

Als u de aangepaste configuratieprovider wilt gebruiken, kunt u het optiespatroon gebruiken. Definieer met de voorbeeld-app een optiesobject dat de widgetinstellingen vertegenwoordigt.

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

Een aanroep voor Configure het registreren van een configuratie-exemplaar, waarmee TOptions een binding wordt uitgevoerd.

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.

Met de voorgaande code wordt het WidgetOptions object geconfigureerd vanuit de "WidgetOptions" sectie van de configuratie. Dit maakt het optiespatroon mogelijk, waarbij een afhankelijkheidsinjectie gereed IOptions<WidgetOptions> wordt weergegeven voor de EF-instellingen. De opties worden uiteindelijk geleverd door de aangepaste configuratieprovider.

Zie ook