Базовая конфигурация ASP.NET: добавление конфигураций в одну строку кода

В течение последних нескольких лет я работал с Sitecore CMS и теперь вернулся к «чистой» разработке ASP.Net MVC. Мне очень нравится, как Sitecore CMS работает с конфигурационными файлами

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

Возвращаясь к чистому ядру ASP.Net MVC — вы можете добавлять файлы в конфигурацию один за другим. Все остальные будут объединены в окончательный конфиг. И это выглядит нормально, пока вы не разработаете приложение с широкими возможностями настройки (например, CMS) и не получите сотни конфигурационных файлов.

var builder = WebApplication.Create(args);
builder.Configuration
     .AddJsonFiles("Configs\\config1.json")
     .AddJsonFiles("Configs\\config2.json")

Существует 3 типа файлов конфигурации, которые вы можете добавить в ASP.Net Core «из коробки»: .json, .xml и .iniДавайте создадим методы расширения для IConfigurationBuilder, которые позволяют включать несколько файлов конфигурации этих типов одной строкой кода в Program.cs.

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

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

  1. Начнем с создания общедоступного статического класса ConfigurationExtensions (естественно, подойдет любое имя). Класс можно создать в любом проекте вашего решения. В случае, если это не тип проекта ASP.Net Core, вам может потребоваться установить дополнительные пакеты nuget для общедоступных методов, таких как Microsoft.Extensions.Configuration.Json и т. д.
  2. Повторно используем метод GetAvailableFiles().
  3. Добавляем  делегата для переноса «однофайловых» методов  IConfigurationBuilder  ( AddConfigFileDelegate  )

private delegate IConfigurationBuilder AddConfigFileDelegate(string filename, bool optional, bool reloadOnChange);

4. Подключаем общую логику добавления файлов в IConfigurationBuilder ( метод AddConfigFiles() )

private static void AddConfigFiles(string  relativePath, string filenamePattern, AddConfigFileDelegate addConfigFile)
{
    var appAssembly = System.Reflection.Assembly.GetEntryAssembly();
    if (appAssembly != null)   
      {
           var appRoot = System.IO.Path.GetDirectoryName(appAssembly.Location);
           if (!string.IsNullOrEmpty(appRoot))
             {
                  var configsFolder = System.IO.Path.Combine(appRoot, relativePath);
                  var files = GetAvailableFiles(configsFolder, filenamePattern, includeSubdirectories: true);
                  foreach (var fileName in files)
                      {
                          addConfigFile(filename: fileName, optional: true, reloadOnChange: rtue);
                      }
             }
      }
}

5. Добавляем общедоступные методы для расширения функциональности IConfigurationBuilder  — AddJsonFiles, AddXmlFiles и AddIniFiles.filenamePattern по умолчанию

public static IConfigurationBuilder AddJsonFiles(this IConfigurationBuilder builder, string relativePath = "", string filenamePattern = "*.json"
{
     var addJsonFileDelegate = new AddConfigFileDelegate(builder.AddJsonFile);
     AddConfigFiles(relativePath, filenamePattern, AddJsonFileDelegate);
     return builder;
}

Это позволяет нам использовать расширение следующим образом:

builder.Configuration
     .AddJsonFiles("Configs", filenamePattern: "*.json");

Все файлы будут добавлены в алфавитном порядке. Вам также разрешено фильтровать файлы в каталогах не только по расширению.

Полный исходный код представлен ниже:

using Microsoft.Extensions.Configuration;

namespace Website.Extensions
{
    public static class ConfigurationExtensions
    {
        /// <summary>
        /// Lists all files in directory and subdirectories
        /// </summary>
        /// <param name="directory">Directory path to search in</param>
        /// <param name="searchPattern">File search pattern</param>
        /// <param name="includeSubdirectories"></param>
        /// <returns>List of file paths</returns>
        private static IEnumerable<string> GetAvailableFiles(string directory, string searchPattern, bool includeSubdirectories = true)
        {
            var result = new List<string>();
            try
            {
                if (includeSubdirectories)
                {
                    var subDirs = System.IO.Directory.GetDirectories(directory);
                    foreach (var subDir in subDirs)
                    {
                        var subDirFiles = GetAvailableFiles(subDir, searchPattern, includeSubdirectories);
                        result.AddRange(subDirFiles);
                    }
                }
                var dirFiles = System.IO.Directory.GetFiles(directory, searchPattern, SearchOption.TopDirectoryOnly);
                result.AddRange(dirFiles);
            }
            catch (UnauthorizedAccessException)
            {
                // Unable to list directory
            }
            return result;
        }

        /// <summary>
        /// Delegate to pass one of IConfigurationBuilder.AddJsonFile, IConfigurationBuilder.AddXmlFile, IConfigurationBuilder.AddIniFile to AddConfigFiles method
        /// </summary>
        private delegate IConfigurationBuilder AddConfigFileDelegate(string filename, bool optional, bool reloadOnChange);

        /// <summary>
        /// Private method with shared logic of config adding
        /// </summary>
        private static void AddConfigFiles(string relativePath, string filenamePattern, AddConfigFileDelegate addConfigFile)
        {
            var appAssembly = System.Reflection.Assembly.GetEntryAssembly();
            if (appAssembly != null)
            {
                var appRoot = System.IO.Path.GetDirectoryName(appAssembly.Location);
                if (!string.IsNullOrEmpty(appRoot))
                {
                    var configsFolder = System.IO.Path.Combine(appRoot, relativePath);
                    var files = GetAvailableFiles(configsFolder, filenamePattern, includeSubdirectories: true);
                    foreach (var fileName in files)
                    {
                        addConfigFile(filename: fileName, optional: true, reloadOnChange: true);
                    }
                }
            }
        }

        /// <summary>
        /// Adds all config files to ConfigurationBuilder as JSON config source
        /// </summary>
        /// <param name="relativePath">Directory to find. Root app directory if null or empty.</param>
        /// <param name="filenamePattern">Filename pattern to search.</param>
        public static IConfigurationBuilder AddJsonFiles(this IConfigurationBuilder builder, string relativePath = "", string filenamePattern = "*.json")
        {
            var addJsonFileDelegate = new AddConfigFileDelegate(builder.AddJsonFile);
            AddConfigFiles(relativePath, filenamePattern, addJsonFileDelegate);
            return builder;
        }

        /// <summary>
        /// Adds all config files to ConfigurationBuilder as XML config source
        /// </summary>
        /// <param name="relativePath">Directory to find. Root app directory if null or empty.</param>
        /// <param name="filenamePattern">Filename pattern to search.</param>
        public static IConfigurationBuilder AddXmlFiles(this IConfigurationBuilder builder, string relativePath = "", string filenamePattern = "*.xml")
        {
            var addXmlFileDelegate = new AddConfigFileDelegate(builder.AddXmlFile);
            AddConfigFiles(relativePath, filenamePattern, addXmlFileDelegate);
            return builder;
        }

        /// <summary>
        /// Adds all config files to ConfigurationBuilder as INI config source
        /// </summary>
        /// <param name="relativePath">Directory to find. Root app directory if null or empty.</param>
        /// <param name="filenamePattern">Filename pattern to search.</param>
        public static IConfigurationBuilder AddIniFiles(this IConfigurationBuilder builder, string relativePath = "", string filenamePattern = "*.ini")
        {
            var addIniFileDelegate = new AddConfigFileDelegate(builder.AddIniFile);
            AddConfigFiles(relativePath, filenamePattern, addIniFileDelegate);
            return builder;
        }
    }
}