Создание собственного провайдера конфигурации и его использование

Имеющихся провайдеров конфигурации в ASP.NET Core вполне достаточно, чтобы покрыть максимум потребностей разработчиков. При этом, возможны ситуации, когда готовых провайдеров может не хватить. Например, нам может потребоваться получать конфигурацию из файла совершенно уникального формата или, наоборот, создать свой уникальный формат хранения конфигурации.

Пример «нестандартного» файла конфигурации

В качестве примера для этого раздела я буду использовать реально используемый на сегодняшний день файл конфигурации специализированного приложения, с которым имел возможность работать. Вот как выглядит такой файл:

CO STARTING                                                                                                                            
TITLEONE A Simple Example Problem   
MODELOPT  CONC   FLAT   AVERTIME  1  3  8  24  PERIOD                                                                                                             
POLLUTID  SO2                                                                                                                       
RUNORNOT  RUN   
ERRORFIL  ERRORS.OUT
CO FINISHED

Здесь название секции всегда состоит из двух символов, например, «CO» (могут использоваться и другие имена секций, например SO, EU и т.д.). Начало секции определяется строкой STARTING, а окончание – FINISHED. При этом имя каждого параметра имеет строго заданную длину – 8 символов, а значениями параметров могут выступать как строки, так и числа, массивы чисел и строк.  Очевидно, что для чтения такой конфигурации нам потребуется создать свой собственный провайдер конфигурации.

Чтобы создать свой провайдер конфигурации нам необходимо:

  1. разработать класс-наследник ConfigurationProvider — сам провайдер конфигурации
  2. разработать класс, реализующий интерфейс IConfigurationSource, который будет определять источник конфигурации
  3. разработать метод расширения для добавления нового провайдера конфигурации в приложение.

Рассмотрим каждый элемент этого списка по-отдельности. Для этого создадим новое приложение ASP.NET Core Web API на основе контроллеров.

Разработка провайдера конфигурации

Класс пользовательского провайдера конфигурации должен наследоваться от базового класса –  ConfigurationProvider. Этот класс содержит ряд виртуальных методов, которые мы можем переопределить в своем классе

Метод Описание
bool TryGet(string key, out string? value) Пытается получить значение настройки с именем ключа key
void Set(string key, string? value) Устанавливает для настройки с именем key новое значение value
void Load() Загружает конфигурацию из источника. В этом методе настройки разбиваются на пары «ключ — значение».
IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys,string? parentPath) Получает список ключей для конфигурации

Для работы с ключами и их значениями используется поле Data класса:

protected IDictionary<string, string?> Data { get; set; }

Для работы поставщика конфигурации обязательным является переопределение метода Load(). В листинге представлен весь код класса нашего поставщика конфигурации.

public class CustomConfigurationProvider : ConfigurationProvider
{
    public string FilePath { get; set; }
    public CustomConfigurationProvider(string filePath) : base()
    {
        FilePath = filePath;
    }

    public override void Load()
    {
        if (File.Exists(FilePath) == false)
            throw new FileNotFoundException(FilePath);

        using StreamReader sr = new(FilePath);
        var dataStr = sr.ReadLine();
        string currentSection = string.Empty;
        while (dataStr != null)
        {
            dataStr = dataStr.Trim();
            if (dataStr.Contains("STARTING"))
            {
                currentSection = dataStr[..2];
            }
            else
            {
                if (dataStr.Contains($"{currentSection} FINISHED"))
                {
                    currentSection = string.Empty;
                }
                else
                    if (currentSection == string.Empty)
                {
                    throw new Exception("Ошибка: не обнаружено начало секции");
                }
                else
                {
                    //разбиваем строку на две: первая - имя настройки, вторая - значение
                    string[] option = dataStr.Split([' '], 2);
                    if (option.Length < 2)
                    {
                        throw new Exception($"Ошибка: в секции {currentSection} обнаружены ошибки записи настроек");
                    }

                    var key = $"{currentSection}:{option[0]}";
                    Data.Add(key, option[1]);
                }
            }
            dataStr = sr.ReadLine();
        }
    }
}

В методе Load() мы, используя цикл считываем каждую строку файла и определяем её назначение в конфигурации – начало секции, окончание секции или же конкретная настройка со значением. Чтобы записать очередную настройку в словарь Data используется следующий формат: [Имя_секции]:[строка_со_значением].

Разработка класса для определения источника конфигурации

Источник конфигурации должен реализовать интерфейс IConfigurationSource, в частности — метод Build(). В этот метод в качестве параметра передается строитель конфигурации (IConfigurationBuilder). Так как мы создаем, по сути, файловый провайдер конфигурации, то класс, определяющий источник конфигурации будет выглядеть следующим образом

public class CustomConfigurationSource : IConfigurationSource
{
    public string FilePath { get; }
    public CustomConfigurationSource(string filename)
    {
        FilePath = filename;
    }
    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        string filePath = builder.GetFileProvider()
                                 .GetFileInfo(FilePath)
                                 .PhysicalPath;
        return new CustomConfigurationProvider(filePath);
    }
}

Здесь относительный путь файла, содержащего конфигурацию, передается в класс источника через конструктор и хранится в свойстве FilePath. В методе Build() мы получаем полный путь к файлу и передаем его в конструктор CustomConfigurationProvider.

Теперь осталось написать метод расширения для IConfigurationBuilder, который позволит добавлять текстовые файлы конфигурации в наше приложение.

Разработка метода расширения для IConfigurationBuilder

Метод расширения должен добавить новый источник конфигурации в менеджер конфигурации. То есть класс, содержащий метод расширения может выглядеть следующим образом:

public static class CustomConfigurationExtensions
{
    public static IConfigurationBuilder AddCustomFile(this IConfigurationBuilder builder, string fileName)
    {
        ArgumentNullException.ThrowIfNull(builder);
        if (string.IsNullOrEmpty(fileName))
        {
            throw new ArgumentException("Путь к файлу не указан");
        }
        return builder.Add(new CustomConfigurationSource(fileName));
    }
}


Теперь нам остается только воспользоваться этим методом расширения в нашем приложении.

Использование собственного источника конфигурации

Создадим в папке проекта новый текстовый файл с именем «properties.txt» и добавим в него настройки, представленные выше. Добавим этот файл в конфигурацию приложения, используя наш метод расширения. Изменим код файла Program.cs как показано ниже

using WebApplication2;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddCustomFile("properties.txt");

builder.Services.AddControllers();

var app = builder.Build();

var property = app.Configuration.GetSection("CO").GetValue<string>("TITLEONE");
app.Logger.LogInformation($"Значение настройки TITLEONE = {property}");


app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

После того, как файл конфигурации добавлен, мы можем использовать настройки из него в любом месте нашего приложения. Так, мы считываем значение настройки TITLEONE после построения приложения и выводим значение настройки в лог (в нашем случае – в консоль). Теперь запустим приложение и в консоли увидим значение настройки

Как можно видеть, провайдер прочитал настройки из нашего файла и теперь мы можем их использовать как нам необходимо – читать значения по одному или же спроецировать все настройки на свой класс.

Итого

Чтобы создать свой провайдер конфигурации нам необходимо: разработать класс-наследник ConfigurationProvider — сам провайдер конфигурации, разработать класс, реализующий интерфейс IConfigurationSource, который будет определять источник конфигурации, разработать метод расширения для добавления нового провайдера конфигурации в приложение.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии