Конфигурация

Steve Smith и Daniel Roth

ASP.NET Core поддерживает различные варианты конфигурации. Данные по конфигурации могут быть получены из файлов при помощи встроенной поддержки форматов JSON, XML и, а также из переменных среды, аргументов командной строки и коллекции in-memory. Вы также можете написать свой собственный пользовательский провайдер конфигурации.

Скачайте пример.

Настройка конфигурации

Конфигурация в ASP.NET Core отличается от конфигурации в предыдущих версиях ASP.NET, которая зависела от System.Configuration и конфигурационных файлов XML, например, web.config. Новые провайдеры конфигурационной модели предоставляют прямой доступ к имени/значению основных настроек, которые можно получить из множества источников. Приложения и фреймворки могут затем получить доступ к настройкам с помощью паттерна опций.

Для работы с настройками в ASP.NET приложении рекомендуется, чтобы вы создавали экземпляр Configuration в классе Startup. Далее вы используете паттерн опций, чтобы получить доступ к отдельным настройкам.

В своем самом простом проявлении класс Configuration - это просто коллекция источников. Если пара имя/значение записывается в Configuration, она не сохраняется. То есть, записанное значение будет потеряно, когда источник будет снова прочтен.

Вы должны настроить как минимум один источник, чтобы корректно работать с Configuration. В следующем примере показано, как работать с Configuration в данном ключе:

var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection();
var config = builder.Build();
config["somekey"] = "somevalue";

// do some other work

var setting = config["somekey"]; // also returns "somevalue"

Примечание

Вы должны установить как минимум один источник.

Вы можете хранить конфигурационные значения в определенной иерархии, особенно при использовании внешних файлов (например, JSON, XML, INI). В данном случае конфигурационные значения могут быть получены с помощью разделителя :, а отсчет идет от корневого элемента в иерархии. Давайте рассмотрим вот такой пример файла appsettings.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1-26e8893e-d7c0-4fc6-8aab-29b59971d622;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Здесь используется конфигурация для настройки строки соединения. Доступ к настройке DefaultConnection достигается с помощью ключа ConnectionStrings:DefaultConnection или метода расширения :dn:method:`~Microsoft.Extensions.Configuration.ConfigurationExtensions.GetConnectionString` с "DefaultConnection".

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

Примечание

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

Использование встроенных источников

У конфигурационного фреймворка есть встроенная поддержка конфигурационных файлов JSON, XML и INI, поддержка конфигурации in-memory (прямая настройка значений в коде), а также возможность работать с конфигурацией с помощью переменных среды и параметров командной строки. Разработчики могут использовать разные источники конфигурации. По факту, их можно объединять, так что конфигурация по умолчанию будет переписана, если есть настройки из другого источника.

Поддержка дополнительных конфигурационных источников связана с методами расширения. Эти методы могут быть вызваны в экземпляре :dn:class:`~Microsoft.Extensions.Configuration.ConfigurationBuilder` в автономном режиме или связаны в виде гибкого API. Оба эти подхода представлены в примере ниже.

// work with with a builder using multiple calls
var builder = new ConfigurationBuilder();
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonFile("appsettings.json");
var connectionStringConfig = builder.Build();

// chain calls together as a fluent API
var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .AddEntityFrameworkConfig(options =>
        options.UseSqlServer(connectionStringConfig.GetConnectionString("DefaultConnection"))
    )
    .Build();

Важен порядок, в котором объединены провайдеры конфигурации, поскольку это определяет приоритет использования настроек в различных местах. В примере выше, если одни и те же настройки существуют в файле appsettings.json и в переменной среды, будет использоваться настройка из переменной среды. Последний указанный провайдер конфигурации “побеждает”, если настройки существуют в разных местах. По этой причине мы рекомендуем указывать переменные среды последними.

Примечание

Чтобы переписать ключи с помощью переменных среды, которые не поддерживают :, замените их двойным подчеркиванием __.

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

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

    if (env.IsDevelopment())
    {
        // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
        builder.AddUserSecrets();
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

Сервис :dn:iface:`~Microsoft.AspNetCore.Hosting.IHostingEnvironment` используется для определения текущей среды. В среде Development выделенная выше строка кода будет искать файл appsettings.Development.json и использовать его значения, переписывая все иные значения, если таковые существуют. См. Работа с несколькими средами.

При указании файлов как источников конфигурации вы можете дополнительно указать, должны ли изменения в файле влиять на настройки. Это конфигурируется путем передачи значения true параметру reloadOnChange при вызове :dn:method:`~Microsoft.Extensions.Configuration.JsonConfigurationExtensions.AddJsonFile` или схожих методов расширения.

Предупреждение

Вы никогда не должны хранить пароли или другие чувствительные данные в коде провайдера или тексте конфигурационных файлов. Также вы не должны использовать производственные секреты в средах разработки и тестирования. Вместо этого такие секреты должны быть указаны вне проекта, чтобы их нельзя было случайно отправить в репозиторий провайдера. См. Работа с несколькими средами и управление Safe storage of app secrets during development.

Изменить приоритет Configuration можно с помощью значений по умолчанию, которые можно переписать. В этом простом консольном приложении настройка значения по умолчанию для username определена в коллекции in-memory, но она будет переписана, если в приложение передается аргумент командной строки для username. Вы можете просмотреть, сколько конфигурационных источников используется на каждом этапе.

При запуске программа отобразит значение по умолчанию, пока параметр командной строки не перепишет его.

../_images/config-console.png

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

С помощью паттерна Options вы можете легко конвертировать любой класс (или POCO - Plain Old CLR Object) в класс настроек. Мы рекомендуем, чтобы вы создавали объекты настроек, которые соответствуют конкретным функциям вашего приложения, и чтобы они следовали принципу разделения интерфейса Interface Segregation Principle (ISP) (классы зависят только от конфигурационных настроек, которые они используют), а также разделению ответственности Separation of Concerns (настройками для разных частей проекта вы управляете по отдельности, и они не влияют друг на друга).

Простой класс MyOptions показан здесь:

public class MyOptions
{
    public string Option1 { get; set; }
    public int Option2 { get; set; }
}

Опции могут быть внедрены в приложение с помощью :dn:iface:`~Microsoft.Extensions.Options.IOptions\<TOptions>`. Например, следующий контроллер controller использует IOptions<MyOptions>, чтобы получить доступ к нужным настройкам для представления Index:

public class HomeController : Controller
{
    private readonly IOptions<MyOptions> _optionsAccessor;

    public HomeController(IOptions<MyOptions> optionsAccessor)
    {
        _optionsAccessor = optionsAccessor;
    }

    // GET: /<controller>/
    public IActionResult Index() => View(_optionsAccessor.Value);
}

Чтобы настроить :dn:iface:`~Microsoft.Extensions.Options.IOptions\<TOptions>`, вы должны вызвать метод расширения :dn:method:`~Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions` во время запуска метода ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();

Представление Index отображает опции конфигурации:

../_images/index-view.png

Мы настраиваем опции с помощью метода расширения :dn:method:`~Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.Configure\<TOptions>`. Вы можете настраивать опции с помощью делегата или объединив опции с конфигурацией:

public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();

    // Configure MyOptions using config by installing Microsoft.Extensions.Options.ConfigurationExtensions
    services.Configure<MyOptions>(Configuration);

    // Configure MyOptions using code
    services.Configure<MyOptions>(myOptions =>
    {
        myOptions.Option1 = "value1_from_action";
    });

    // Configure MySubOptions using a sub-section of the appsettings.json file
    services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

    // Add framework services.
    services.AddMvc();
}

Если вы объединяете опции с конфигурацией, каждое свойство типа “option” связывается с конфигурационным ключом типа property:subproperty:.... Например, свойство MyOptions.Option1 связано с ключом Option1, который считывается из свойства option1 в appsettings.json. Обратите внимание, что имена конфигурационных ключей зависят от регистра.

Каждый вызов :dn:method:`~Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.Configure\<TOptions>` добавляет :dn:iface:`~Microsoft.Extensions.Options.IConfigureOptions\<TOptions>` для обработки контейнера, который используется сервисом :dn:iface:`~Microsoft.Extensions.Options.IOptions\<TOptions>`, чтобы предоставить конфигурационные опции приложению или фреймворку. Если вы хотите настроить опции другим способом (например, получение опций из базы данных), вы можете использовать метод расширения AddSingleton<TOptions>, чтобы напрямую указать пользовательский сервис :dn:iface:`~Microsoft.Extensions.Options.IConfigureOptions\<TOptions>`.

У вас может быть несколько сервисов :dn:iface:`~Microsoft.Extensions.Options.IConfigureOptions\<TOptions>` для одного типа опций, и все они применяются по очереди. В примере значение Option1 и Option2 указаны в appsettings.json, но значение Option1 переписано делегатом с помощью значения “value1_from_action”

Написание пользовательских провайдеров

В дополнении к использованию встроенных конфигурационных источников вы можете создать свой собственный. Чтобы это сделать, вы просто реализуете интерфейс :dn:iface:`~Microsoft.Extensions.Configuration.IConfigurationSource`, который раскрывает метод :dn:method:`~Microsoft.Extensions.Configuration.IConfigurationSource.Build`. Этот метод настраивает и возвращает :dn:iface:`~Microsoft.Extensions.Configuration.IConfigurationProvider`.

Пример: Настройки Entity Framework

Вы можете сохранять некоторые настройки в базе данных и получать доступ к ним с помощью Entity Framework (EF). Вы можете сохранять такие значения разными способами: от простой таблицы с колонками для имени/значения настройки до отдельных колонок для каждого значения настройки. В этом примере будет создан простой источник конфигурации, который считывает пары имя/значение из базы данных с помощью EF.

Для начала мы создадим простую запись ConfigurationValue для хранения в базе данных значений конфигурации:

public class ConfigurationValue
{
    public string Id { get; set; }
    public string Value { get; set; }
}

Также нам потребуется ConfigurationContext, чтобы хранить и получать доступ к этим значениям:

public class ConfigurationContext : DbContext
{
    public ConfigurationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<ConfigurationValue> Values { get; set; }
}

Создайте EntityFrameworkConfigurationSource, который наследуется от :dn:iface:`~Microsoft.Extensions.Configuration.IConfigurationSource`:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public class EntityFrameworkConfigurationSource : IConfigurationSource
    {
        private readonly Action<DbContextOptionsBuilder> _optionsAction;

        public EntityFrameworkConfigurationSource(Action<DbContextOptionsBuilder> optionsAction)
        {
            _optionsAction = optionsAction;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new EntityFrameworkConfigurationProvider(_optionsAction);
        }
    }
}

Далее, мы создаем пользовательский источник конфигурации, наследуя от :dn:class:`~Microsoft.Extensions.Configuration.ConfigurationProvider`. Данные по конфигурации загружаются после переписывания метода Load, который считывает эти данные из базы данных. В примере источник конфигурации также инициализирует базу данных, если она еще не была создана и заполнена:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public class EntityFrameworkConfigurationProvider : ConfigurationProvider
    {
        public EntityFrameworkConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
        {
            OptionsAction = optionsAction;
        }

        Action<DbContextOptionsBuilder> OptionsAction { get; }

        public override void Load()
        {
            var builder = new DbContextOptionsBuilder<ConfigurationContext>();
            OptionsAction(builder);

            using (var dbContext = new ConfigurationContext(builder.Options))
            {
                dbContext.Database.EnsureCreated();
                Data = !dbContext.Values.Any()
                    ? CreateAndSaveDefaultValues(dbContext)
                    : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
            }
        }

        private static IDictionary<string, string> CreateAndSaveDefaultValues(
            ConfigurationContext dbContext)
        {
            var configValues = new Dictionary<string, string>
                {
                    { "key1", "value_from_ef_1" },
                    { "key2", "value_from_ef_2" }
                };
            dbContext.Values.AddRange(configValues
                .Select(kvp => new ConfigurationValue { Id = kvp.Key, Value = kvp.Value })
                .ToArray());
            dbContext.SaveChanges();
            return configValues;
        }
    }
}

Обратите внимание на значения, которые хранятся в БД (“value_from_ef_1” and “value_from_ef_2”); они представлены в примере ниже.

Мы также добавляем метод расширения AddEntityFrameworkConfigurationSource для добавления провайдера конфигурации:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public static class EntityFrameworkExtensions
    {
        public static IConfigurationBuilder AddEntityFrameworkConfig(
            this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
        {
            return builder.Add(new EntityFrameworkConfigurationSource(setup));
        }
    }
}

Создайте новый :dn:class:`~Microsoft.Extensions.Configuration.ConfigurationBuilder`, чтобы настроить источник конфигурации. Чтобы добавить EntityFrameworkConfigurationSource, вам сперва нужно указать провайдер данных и строку соединения. Как настроить строку соединения? С помощью конфигурации, конечно! Добавьте файл appsettings.json в качестве источника конфигурации для настройки EntityFrameworkConfigurationSource. С помощью повторного использования ConfigurationBuilder любые настройки, указанные в базе данных, будут переписаны настройками из appsettings.json:

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public static class Program
    {
        public static void Main()
        {
            // work with with a builder using multiple calls
            var builder = new ConfigurationBuilder();
            builder.SetBasePath(Directory.GetCurrentDirectory());
            builder.AddJsonFile("appsettings.json");
            var connectionStringConfig = builder.Build();

            // chain calls together as a fluent API
            var config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddEntityFrameworkConfig(options =>
                    options.UseSqlServer(connectionStringConfig.GetConnectionString("DefaultConnection"))
                )
                .Build();

            Console.WriteLine("key1={0}", config["key1"]);
            Console.WriteLine("key2={0}", config["key2"]);
            Console.WriteLine("key3={0}", config["key3"]);
        }
    }
}

Запустите приложение, и вы увидите следующее:

../_images/custom-config.png

Резюме

ASP.NET Core предлагает очень гибкую конфигурационную модель, которая поддерживает разные опции, а также командную строку, опцию in-memory и переменные среды. Вы можете внедрять строго типизированные настройки в свое приложение или фреймворк. Также в можете создавать свои собственные провайдеры настроек, которые могут заменят встроенные провайдеры.

Поделись хорошей новостью с друзьями!
Следи за новостями!