Comparteix via


Implementación de un proveedor de configuración personalizado en .NET

Hay muchos proveedores de configuración disponibles para orígenes de configuración comunes, como archivos JSON, XML e INI. Es posible que tenga que implementar un proveedor de configuración personalizado cuando uno de los proveedores disponibles no se adapte a sus necesidades de aplicación. En este artículo, aprenderá a implementar un proveedor de configuración personalizado que se basa en una base de datos como origen de configuración.

Proveedores de configuración personalizada

La aplicación de ejemplo muestra cómo crear un proveedor de configuración básico que lee pares clave-valor de configuración de una base de datos mediante Entity Framework (EF) Core.

El proveedor tiene las siguientes características:

  • La base de datos en memoria de EF se usa para fines de demostración.
    • Para usar una base de datos que requiera una cadena de conexión, obtenga una cadena de conexión de una configuración provisional.
  • El proveedor lee una tabla de base de datos en la configuración en el inicio. El proveedor no consulta la base de datos por clave.
  • No se implementa la recarga al realizar cambios, por lo que actualizar la base de datos después de iniciar la aplicación no afectará su configuración.

Defina una Settings entidad de tipo de registro para almacenar valores de configuración en la base de datos. Por ejemplo, podría agregar un archivo Settings.cs en la carpeta Models :

namespace CustomProvider.Example.Models;

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

Para obtener información sobre los tipos de registro, vea Tipos de registro en C#.

Agregue un EntityConfigurationContext para almacenar y tener acceso a los valores configurados.

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

Al sobrescribir OnConfiguring(DbContextOptionsBuilder), se puede usar la conexión de base de datos adecuada. Por ejemplo, si se proporcionó una cadena de conexión, podría conectarse a SQL Server; de lo contrario, podría confiar en una base de datos en memoria.

Cree una clase que implemente 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);
}

Cree el proveedor de configuración personalizado heredando de ConfigurationProvider. El proveedor de configuración inicializa la base de datos cuando está vacía. Puesto que las claves de configuración no distinguen entre mayúsculas y minúsculas, el diccionario empleado para iniciar la base de datos se crea con el comparador que tampoco hace tal distinción (StringComparer.OrdinalIgnoreCase).

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

Un AddEntityConfiguration método de extensión permite agregar el origen de configuración a la instancia subyacente ConfigurationManager .

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

Dado que ConfigurationManager es una implementación de IConfigurationBuilder y IConfigurationRoot, el método de extensión de IConfigurationRoot puede tener acceso a la configuración de cadenas de conexión y agregar EntityConfigurationSource.

En el código siguiente se muestra cómo usar el personalizado EntityConfigurationProvider en 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.

Consumo de proveedor

Para consumir el proveedor de configuración personalizado, puede usar el patrón de opciones. Con la aplicación de ejemplo en contexto, defina un objeto de opciones para representar la configuración del 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!;
}

Una llamada a Configure registra una instancia de configuración, a la que TOptions se enlaza.

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.

El código anterior configura el WidgetOptions objeto desde la "WidgetOptions" sección de la configuración. Esto habilita el patrón de opciones, exponiendo una representación de los ajustes de EF lista para la inyección de dependencias IOptions<WidgetOptions>. Finalmente, las opciones se proporcionan del proveedor de configuración personalizado.

Consulte también