Содержание
По умолчанию ASP.NET Core предоставляет довольно большой перечень поставщиков конфигурации приложения — для работы с аргументами командной строки, переменными среды, файлами json, xml, ini и т.д. Этого перечня вполне достаточно, чтобы покрыть максимум потребностей разработчиков. Однако, можно столкнуться с ситуацией, когда и этого богатого функционала будет недостаточно для работы и потребуется какое-то нестандартное решение для получения конфигурации.
Пример нестандартного конфигурационного файла
Рассмотрим пример, когда файл, содержащий конфигурацию приложения имеет нестандартный формат. Это реально используемый файл конфигурации в специальном программном обеспечении, которое широко используется специалистами в Европе.
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
), начало секции обозначается строкой [имя_секции] STARTING
, а окончание — [имя_секции] FINISHED
. Очевидно, что, если мы захотим использовать такие файлы конфигурации в приложении ASP.NET Core, то нам потребуется создание собственного провайдера конфигурации. Давайте попробуем это сделать.
Создание собственного провайдера конфигурации
Для создания конфигурации нам потребуется разработать три компонента:
- Класс-наследник
ConfigurationProvider
— сам провайдер конфигурации - Класс, реализующий интерфейс
IConfigurationSource
- класс, который добавляет метод расширения к объекту
IConfiguration
Разработка провайдера конфигурации
Создадим класс-наследник от ConfigurationProvider
, который будет разбирать файл конфигурации, представленный выше:
public class TxtConfigurationProvider : ConfigurationProvider { public string FilePath { get; set; } public TxtConfigurationProvider(string filePath) { FilePath = filePath; } public override void Load() { if (File.Exists(FilePath)==false) throw new FileNotFoundException(FilePath); Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase); using (StreamReader sr = new StreamReader(FilePath)) { var dataStr = sr.ReadLine(); string currentSection = string.Empty; while (dataStr != null) { dataStr = dataStr.Trim(); if (dataStr.Contains("STARTING"))//строка содержит начало секции currentSection = dataStr.Substring(0, 2); else { if (dataStr.Contains($"{currentSection} FINISHED")) //строка содержит окончание секции currentSection = string.Empty; else if (currentSection == string.Empty) //строка содержит какую-то настройку из секции throw new Exception("Ошибка: не обнаружено начало секции"); else { string[] option = dataStr.Split(new char[] {' '}, 2);//разбиваем строку на две: первая - имя настройки, вторая - значение if (option.Length < 2) throw new Exception($"Ошибка: в секции {currentSection} обнаружены ошибки записи настроек"); var key = $"{currentSection}:{option[0]}"; Data.Add(key, option[1]); } } dataStr = sr.ReadLine(); } } } }
У класса мы перегрузили метод Load
, который, собственно и загружает конфигурацию из файла. В этом методе мы используем свойство класса ConfigurationProvider Data
, которое должно представлять собой словарь Dictionary<string, string?>
где ключ — это имя опции. Далее мы построчно читаем файл и записываем в Data
полученные значения. Здесь стоит обратить внимание на строки:
var key = $"{currentSection}:{option[0]}"; Data.Add(key, option[1]);
Так как наша конфигурация имеет секции, то, в общем случае, ключ составляется следующим образом [название_секции]:[название_подсекции]:[название_опции]
. В нашем случае файл конфигурации не может иметь подсекций, но каждая опция обязательно располагается в своей секции, поэтому ключ у нас записывается как [название_секции]:[название_опции]
.
Теперь, когда у нас есть провайдер конфигурации, мы можем создать источник конфигурации, то есть разработать класс, реализующий интерфейс IConfigurationSource
Разработка класса источника конфигурации
Источник конфигурации должен реализовать интерфейс IConfigurationSource
, в частности — метод Build
. В этот метод в качестве параметра передается строитель конфигурации (IConfigurationBuilder
).
public class TxtConfigurationSource : IConfigurationSource { public string FilePath { get; } public TxtConfigurationSource(string filename) { FilePath = filename; } public IConfigurationProvider Build(IConfigurationBuilder builder) { string filePath = builder.GetFileProvider().GetFileInfo(FilePath).PhysicalPath; return new TxtConfigurationProvider(filePath); } }
Краткое название файла (его относительный путь) передается в класс источника через конструктор и хранится в свойстве FilePath
. В методе Build
мы получаем полный путь к файлу и передаем его в конструктор TxtConfigurationProvider
. Теперь осталось написать метод расширения для IСonfiguration
, который позволит добавлять текстовые файлы конфигурации в наше приложение.
Метод расширения для IConfiguration
Подобные методы расширения мы уже писали для других интерфейсов, поэтому создать очередной — не составит особого труда:
public static class TxtConfigurationExtensions { public static IConfigurationBuilder AddTxtFile(this IConfigurationBuilder builder, string path) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (string.IsNullOrEmpty(path)) { throw new ArgumentException("Путь к файлу не указан"); } var source = new TxtConfigurationSource(path); builder.Add(source); return builder; } }
Этот класс определяет для объекта IConfigurationBuilder
метод расширения AddTxtFile()
, в котором создается источник конфигурации TxtConfigurationSource
, который затем добавляется к строителю конфигурации. Протестируем полученное решение.
Тестирование собственного провайдера конфигурации
Создадим текстовый файл с именем options.txt и добавим в него конфигурацию, как было показано выше:
CO STARTING TITLEONE A Simple Example Problem for the AERMOD Model with PRIME MODELOPT CONC FLAT AVERTIME 1 3 8 24 PERIOD POLLUTID SO2 RUNORNOT RUN EVENTFIL aertest_evt.inp ERRORFIL ERRORS.OUT CO FINISHED
Теперь, спроецируем секцию OP на класс C#, как мы это делали ранее. Класс для хранения опций:
public class TxtOptions { public string? Titleone { get; set; } public string? ModelOpt { get; set; } public string? Avertime { get; set; } public string? PollutId { get; set; } public string? RunOrNot { get; set; } public string? EventFil { get; set; } public string? ErrorFil { get; set; } }
Код метода Main
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddTxtFile("options.txt"); var app = builder.Build(); var section = app.Configuration.GetSection("co").Get<TxtOptions>(); app.MapGet("/", () => $"{section.Titleone} - {section.Avertime}"); app.Run(); }
Здесь мы добавили новую конфигурацию:
builder.Configuration.AddTxtFile("options.txt");
спроецировали секцию OP на класс TxtOptions
var section = app.Configuration.GetSection("co").Get<TxtOptions>();
и добавили новую конечную точку в которой перечислили несколько настроек:
app.MapGet("/", () => $"{section.Titleone} - {section.Avertime}");
Результат
Итого
Создание собственного провайдера конфигурации ASP.NET Core позволяет добавлять в приложение конфигурацию в любом формате. Для создания конфигурации нам потребовалось разработать три компонента: класс-наследник ConfigurationProvider
, класс, реализующий интерфейс IConfigurationSource
и класс, который добавляет метод расширения к объекту IConfiguration
.