Implémenter un fournisseur de configuration personnalisé dans .NET

Il existe de nombreux fournisseurs de configuration disponibles pour les sources de configuration courantes telles que les fichiers JSON, XML et INI. Vous devrez peut-être implémenter un fournisseur de configuration personnalisé lorsque l’un des fournisseurs disponibles ne répond pas aux besoins de votre application. Dans cet article, vous allez apprendre à implémenter un fournisseur de configuration personnalisé qui s’appuie sur une base de données comme source de configuration.

Fournisseur de configuration personnalisé

L’exemple d’application montre comment créer un fournisseur de configuration de base qui lit les paires clé-valeur de configuration à partir d’une base de données à l’aide d’Entity Framework (EF).

Le fournisseur présente les caractéristiques suivantes :

  • La base de données en mémoire EF est utilisée à des fins de démonstration.
    • Pour utiliser une base de données qui nécessite une chaîne de connexion, obtenez une chaîne de connexion à partir d’une configuration intermédiaire.
  • Le fournisseur lit une table de base de données dans la configuration au démarrage. Le fournisseur n’interroge pas la base de données par clé.
  • Le rechargement en cas de changement n’est pas implémenté, par conséquent, la mise à jour de la base de données après le démarrage de l’application n’a aucun effet sur la configuration de l’application.

Définissez une entité de type d’enregistrement Settings pour le stockage des valeurs de configuration dans la base de données. Par exemple, vous pouvez ajouter un fichier Settings.cs dans votre dossier Modèles :

namespace CustomProvider.Example.Models;

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

Pour plus d’informations sur les types d’enregistrement, consultez Types d’enregistrement en C#.

Ajoutez un EntityConfigurationContext pour stocker les valeurs configurées et y accéder.

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

En remplaçant, OnConfiguring(DbContextOptionsBuilder) vous pouvez utiliser la connexion de base de données appropriée. Par exemple, si une chaîne de connexion a été fournie, vous pouvez vous connecter à SQL Server, sinon, vous pouvez vous appuyer sur une base de données en mémoire.

Créez une classe qui implémente 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);
}

Créez le fournisseur de configuration personnalisé en héritant de ConfigurationProvider. Le fournisseur de configuration initialise la base de données quand elle est vide. Étant donné que les clés de configuration ne respectent pas la casse, le dictionnaire utilisé pour initialiser la base de données est créé avec le comparateur ne respectant pas la casse (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;
    }
}

Une méthode d’extension AddEntityConfiguration permet d’ajouter la source de configuration à l’instance ConfigurationManager sous-jacente.

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

Comme ConfigurationManager est une implémentation à la fois de IConfigurationBuilder et de IConfigurationRoot, la méthode d’extension peut accéder à la configuration des chaînes de connexion s et ajouter la EntityConfigurationSource.

Le code suivant montre comment utiliser le EntityConfigurationProvider personnalisé dans 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.

Utiliser le fournisseur

Pour utiliser le fournisseur de configuration personnalisé, vous pouvez utiliser le modèle d’options. Une fois l’exemple d’application en place, définissez un objet d’options pour représenter les paramètres du 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!;
}

Un appel à Configure inscrit une instance de configuration à laquelle TOptions se lie.

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.

Le code précédent configure l’objet WidgetOptions à partir de la section "WidgetOptions" de la configuration. Cela active le modèle d’options, exposant une représentation IOptions<WidgetOptions> prête pour l’injection de dépendances des paramètres EF. Les options sont finalement fournies à partir du fournisseur de configuration personnalisé.

Voir aussi