Создаем собственный провайдер конфигурации в ASP.NET Core

ASP.NET Core использует новую модель конфигурирования на основе IOptions<>, о чем я писал ранее. Эта модель позволяет считывать конфигурационные параметры отдельными частями приложения. Но где эти данные размещены? Давайте разберемся с этим вопросом.

Startup.cs

Для начала заглянем в файл Startup.cs — файл, с которого начинается запуск приложения. В стандартном шаблоне проекта ASP.NET Core, в конструкторе класса Startup создается объект ConfigurationBuilder и на его основе строится объект с конфигурационными данными. Этот объект сохраняется в локальное свойство и фактически хранит словарь конфигурационных параметров.

public class Startup
{
  public Startup(IHostingEnvironment env)
  {
    IConfigurationBuilder builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", true, true);
    Configuration = builder.Build();
  }

  public IConfigurationRoot Configuration { get; }

Внутри класса Startup это свойство затем неоднократно используется для настройки отдельных частей приложения — об этом я писал в предыдущей записи.

Источники данных для конфигурации

Из кода Startup.cs можно понять, что ConfigurationBuilder'у передается файл appsettings.json, в котором и хранятся настройки приложения. Чем хорош механизм конфигурирования ASP.NET Core — это возможностью использовать одновременно несколько источников данных для конфигурации. Например, можно хранить конфигурацию в нескольких JSON-файлах:

public Startup(IHostingEnvironment env)
{
  IConfigurationBuilder builder = new ConfigurationBuilder()
      .SetBasePath(env.ContentRootPath)
      .AddJsonFile("appsettings.json", true, true)
      .AddJsonFile("another-settings.json", true, true);

Можно также использовать разные типы источников. Например, часть конфигурации хранить в JSON, а другую — в XML:

public Startup(IHostingEnvironment env)
{
  IConfigurationBuilder builder = new ConfigurationBuilder()
      .SetBasePath(env.ContentRootPath)
      .AddJsonFile("appsettings.json", true, true)
      .AddXmlFile("appsettings.xml", true, true);

Из коробки ASP.NET Core предлагает несколько различных способов задания источников конфигурации:

  • CommandLine — извлекает конфигурационные параметры из командной строки при запуске веб-сервера
  • EnvironmentVariables — извлекает конфигурационные параметры из переменных окружения операционной системы
  • JSON, XML, Ini — позволяет использовать файлы JSON, XML и INI для хранения конфигруационных параметров
  • UserSecrets — позволяет использовать специальное защищенное хранилище для хранения конфигурационных параметров

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

Собственный источник конфигурации ASP.NET Core

В качестве учебного примера, создадим источник данных, позволяющий хранить конфигурацию в файлах формата YAML.

Реализация собственного источника конфигурации начинается с создания класса, реализующего интерфейс IConfigurationSource:

public class YamlConfigurationSource : IConfigurationSource
{
  public YamlConfigurationSource(string fileName)
  {
    FileName = fileName;
  }

  public string FileName { get; }

  public IConfigurationProvider Build(IConfigurationBuilder builder)
  {
    return new YamlConfigurationProvider(this);
  }
}

Основная задача этого класса заключается в получении исходных данных (например, имени конфигруационного файла) и создании объекта ConfigurationProvider для данного источника. Реализацией провайдера займемся чуть позже, а пока давайте сразу добавим методы расширения, чтобы можно было удобно использовать этот источник данных в Startup.cs:

  public static class YamlConfigurationSourceExtensions
  {
    public static IConfigurationBuilder AddYamlFile(
      this IConfigurationBuilder builder, string filename)
    {
      return builder.Add(new YamlConfigurationSource(filename));
    }
  }

Для реализации провайдера нам потребуется доступ к файловой системе. Поэтому при создании провайдера передадим ссылку на него. Код YamlConfigurationSource изменится следующим образом:

public class YamlConfigurationSource : IConfigurationSource
{
  public YamlConfigurationSource(string fileName)
  {
    FileName = fileName;
  }

  public string FileName { get; }

  public IConfigurationProvider Build(IConfigurationBuilder builder)
  {
    return new YamlConfigurationProvider(this, builder.GetFileProvider());
  }
}

Осталось реализовать провайдер для доступа к Yaml. Для этого создадим класс-наследник ConfigurationProvider. Для загрузки данных из источника следует реализовать метод Load:

public class YamlConfigurationProvider : ConfigurationProvider
{
  private readonly IFileProvider _fileProvider;
  private readonly YamlConfigurationSource _source;

  public YamlConfigurationProvider(YamlConfigurationSource source, IFileProvider fileProvider)
  {
    _source = source;
    _fileProvider = fileProvider;
  }

  public override void Load()
  {
  }
}

Для работы с Yaml-файлами воспользуемся библиотекой YamlDotNet. Вся суть конфигурационного провайдера сводится к считыванию источника и сохранении конфигурационных значений в служебном словаре, доступном через свойство Data.

public class YamlConfigurationProvider : ConfigurationProvider
{
  private readonly IFileProvider _fileProvider;
  private readonly YamlConfigurationSource _source;

  public YamlConfigurationProvider(YamlConfigurationSource source, IFileProvider fileProvider)
  {
    _source = source;
    _fileProvider = fileProvider;
  }

  public override void Load()
  {
    IFileInfo file = _fileProvider.GetFileInfo(_source.FileName);
    using (Stream stream = file.CreateReadStream())
    {
      using (TextReader reader = new StreamReader(stream))
      {
        Dictionary<object, object> document = new DeserializerBuilder()
          .Build()
          .Deserialize(reader) as Dictionary<object, object>;

        if (document != null)
        {
          foreach (KeyValuePair<object, object> item in document)
          {
            Data[item.Key.ToString()] = item.Value.ToString();
          }
        }
      }
    }
  }
}

Провайдер готов. Осталось подключить его к приложению в Startup.cs:

public class Startup
{
  public Startup(IHostingEnvironment env)
  {
    IConfigurationBuilder builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", true, true)
        .AddYamlFile("config.yml");
    Configuration = builder.Build();
  }

Заключение

Приведенный выше пример показывает как за несколько минут можно добавить собственный конфигурационный провайдер в приложение ASP.NET Core.

Обратите внимание, что этот пример годится только для учебных целей. Например, в нем не реализована обработка вложенных свойств YAML. Кроме того, для реализации конфигурационных источников, которые хранят свои данные в файлах существует ещё одна абстракция - FileConfigurationProvider и FileConfigurationSource. Кроме всего прочего в них реализована возможность отслеживания изменений в конфигурационных файлах. Поэтому, если ваш источник конфигурации использует файлы, то собственный провайдер лучше создавать на основе FileConfigurationProvider.