Реализация пользовательского поставщика конфигурации в .NET
Есть много поставщиков конфигурации, доступных для распространенных источников конфигурации, включая файлы JSON, XML и INI. Вам может потребоваться реализовать свой поставщик конфигурации, если ни один из доступных поставщиков не соответствует потребностям вашего приложения. В этой статье показано, как реализовать настраиваемый поставщик конфигурации, который использует базу данных в качестве источника конфигурации.
Поставщик пользовательской конфигурации
Пример приложения демонстрирует, как создать базовый поставщик конфигурации, который считывает пары "ключ — значение" конфигурации из базы данных, используя Entity Framework (EF) Core.
Поставщик имеет следующие характеристики.
- База данных в памяти EF используется для демонстрационных целей.
- Чтобы использовать базу данных, для которой требуется строка подключения, необходимо получить эту строку из промежуточной конфигурации.
- Поставщик считывает таблицу базы данных в конфигурации при запуске. Поставщик не запрашивает базу данных для каждого ключа.
- Функция перезагрузки на изменение не реализована, поэтому обновление базы данных после запуска приложения не влияет на конфигурацию приложения.
Определите сущность типа записи Settings
для хранения значений конфигурации в базе данных. Например, можно добавить файл Settings.cs в папку Models:
namespace CustomProvider.Example.Models;
public record Settings(string Id, string? Value);
Сведения о типах записей см. в разделе "Типы записей" в C#.
Добавьте EntityConfigurationContext
в хранилище и обратитесь к настроенным значениям.
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")
};
}
}
За счет переопределения OnConfiguring(DbContextOptionsBuilder) вы можете использовать требуемое подключение к базе данных. Например, если была предоставлена строка подключения, можно подключиться к SQL Server. В противном случае можно полагаться на базу данных в памяти.
Создайте класс, реализующий перехватчик 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);
}
Создайте пользовательский поставщик конфигурации путем наследования от ConfigurationProvider. Поставщик конфигурации инициализирует пустую базу данных. Так как конфигурационные ключи не учитывают регистр, словарь, используемый для инициализации базы данных, создается с помощью функции сравнения без учета регистра (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;
}
}
Метод AddEntityConfiguration
расширения позволяет добавить источник конфигурации в базовый ConfigurationManager
экземпляр.
Расширения и 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 Так как это реализация IConfigurationBuilder и IConfigurationRoot, метод расширения может получить доступ к конфигурации строка подключения и добавить EntityConfigurationSource
.
В следующем коде показано, как использовать пользовательский EntityConfigurationProvider
в 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.
Использование поставщика
Для использования настраиваемого поставщика конфигурации можно применять шаблон параметров. Подготовив пример приложения, задайте объект, который будет представлять параметры мини-приложений.
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!;
}
Вызов регистрации Configure экземпляра конфигурации, к которому TOptions
привязывается.
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.
Приведенный выше код настраивает объект WidgetOptions
из раздела "WidgetOptions"
в конфигурации. Это активирует шаблон параметров, создавая представление параметров IOptions<WidgetOptions>
в Entity Framework, готовое к внедрению зависимостей. В конечном итоге эти параметры предоставляются настраиваемым поставщиком конфигурации.