Содержание
По умолчанию 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.

