Шаблон параметров в ASP.NET Core

Авторы: Кирк Ларкин (Kirk Larkin) и Рик Андерсон (Rick Anderson).

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

В параметрах также предусмотрен механизм для проверки данных конфигурации. Дополнительные сведения см. в разделе Проверка параметров.

В этом разделе приводятся сведения о шаблоне параметров в ASP.NET Core. Сведения об использовании шаблона параметров в консольных приложениях см. в разделе Шаблон параметров в .NET.

Просмотреть или скачать образец кода (как скачивать)

Привязка иерархической конфигурации

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

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

Создайте следующий класс PositionOptions:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

Класс параметров:

  • Должен быть неабстрактным с открытым конструктором без параметров.
  • Все открытые свойства чтения и записи типа привязаны.
  • Поля не привязаны. В приведенном выше коде свойство Position не привязано. Свойство Position используется так, что строку "Position" не требуется жестко кодировать в приложении при привязке класса к поставщику конфигурации.

В приведенном ниже коде

  • Вызывает ConfigurationBinder.Bind для привязки класса PositionOptions к разделу Position.
  • Отображает данные конфигурации Position.
public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

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

ConfigurationBinder.Get<T> привязывает и возвращает указанный тип. Метод ConfigurationBinder.Get<T> может быть более удобным, чем ConfigurationBinder.Bind. В приведенном ниже примере кода демонстрируются способы использования ConfigurationBinder.Get<T> с классом PositionOptions:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

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

Альтернативный подход при использовании *шаблона параметров _ — привязка раздела Position и добавление его в контейнер службы внедрения зависимостей. В следующем коде PositionOptions добавляется в контейнер службы с помощью интерфейса <xref:Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure_> и привязывается к конфигурации:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

С помощью приведенного выше кода следующий код считывает параметры расположения:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

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

Интерфейсы параметров

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Сценарии пост-конфигурации позволяют задать или изменить параметры после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

Интерфейс IOptionsFactory<TOptions> отвечает за создание экземпляров параметров. Он имеет единственный метод Create. При реализации по умолчанию принимаются все зарегистрированные интерфейсы IConfigureOptions<TOptions> и IPostConfigureOptions<TOptions>. Также сначала выполняются все основные настройки, а затем действия после конфигурации. Она различает интерфейсы IConfigureNamedOptions<TOptions> и IConfigureOptions<TOptions> и вызывает только соответствующий интерфейс.

IOptionsMonitorCache<TOptions> используется интерфейсом IOptionsMonitor<TOptions> для записи экземпляров TOptions в кэш. IOptionsMonitorCache<TOptions> делает экземпляры параметров в мониторе недействительными, что приводит к повторному вычислению значений (TryRemove). Значения можно также вводить вручную с помощью TryAdd. Метод Clear используется, если необходимо повторно создать все именованные экземпляры по требованию.

Использование IOptionsSnapshot для чтения обновленных данных

С помощью IOptionsSnapshot<TOptions> параметры вычисляются один раз на каждый запрос при обращении к ним и кэшируются на все время существования запроса. Изменения конфигурации считываются после запуска приложения при использовании поставщиков конфигурации, поддерживающих чтение обновленных значений конфигурации.

Разница между IOptionsMonitor и IOptionsSnapshot:

  • IOptionsMonitor — это одноэлементная служба, которая получает текущие значения параметров в любое время, что особенно полезно в одноэлементных зависимостях.
  • IOptionsSnapshot — это служба с заданной областью действия, предоставляющая моментальный снимок параметров на момент создания объекта IOptionsSnapshot<T>. Моментальные снимки параметров предназначены для использования с временными зависимостями и зависимостями с заданной областью действия.

В приведенном ниже коде используется IOptionsSnapshot<TOptions>.

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

Следующий код регистрирует экземпляр конфигурации, к которому привязывается MyOptions.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

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

IOptionsMonitor

Следующий код регистрирует экземпляр конфигурации, к которому привязывается MyOptions.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

В следующем примере используется IOptionsMonitor<TOptions>.

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

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

Поддержка именованных параметров с использованием IConfigureNamedOptions

Именованные параметры:

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

Рассмотрим следующий файл appsettings.json :

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

Вместо создания двух классов для привязки TopItem:Month и TopItem:Year для каждого раздела используется следующий класс:

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; }
    public string Model { get; set; }
}

Следующий код служит для настройки именованных параметров:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<TopItemSettings>(TopItemSettings.Month,
                                       Configuration.GetSection("TopItem:Month"));
    services.Configure<TopItemSettings>(TopItemSettings.Year,
                                        Configuration.GetSection("TopItem:Year"));

    services.AddRazorPages();
}

Следующий код отображает именованные параметры:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Все параметры являются именованными экземплярами. Экземпляры IConfigureOptions<TOptions> считаются нацеленными на экземпляр Options.DefaultName, который имеет значение string.Empty. Интерфейс IConfigureNamedOptions<TOptions> также реализует интерфейс IConfigureOptions<TOptions>. Реализация IOptionsFactory<TOptions> по умолчанию содержит логику для надлежащего использования каждого экземпляра. Именованный параметр null предназначен для всех именованных экземпляров, а не для какого-то определенного. ConfigureAll и PostConfigureAll следуют этому соглашению.

API OptionsBuilder

OptionsBuilder<TOptions> используется для настройки экземпляров TOptions. OptionsBuilder упрощает создание именованных параметров, так как он является единственным параметром для первоначального вызова AddOptions<TOptions>(string optionsName) и не должен появляться во всех последующих вызовах. Проверка параметров и перегрузки ConfigureOptions, принимающие зависимости службы, доступны только через OptionsBuilder.

OptionsBuilder используется в разделе Проверка параметров.

Дополнительные сведения о добавлении пользовательского репозитория см. в статье Использование AddOptions для настройки пользовательского репозитория.

Использование служб внедрения зависимостей для настройки параметров

Использовать службы, доступные в результате внедрения зависимостей при настройке параметров, можно двумя способами.

  • Передайте делегат конфигурации методу Configure в OptionsBuilder<TOptions>. OptionsBuilder<TOptions> предоставляет перегрузки метода Configure, которые позволяют использовать до пяти служб для настройки параметров:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Создайте тип, реализующий IConfigureOptions<TOptions> или IConfigureNamedOptions<TOptions>, и зарегистрируйте этот тип как службу.

Рекомендуем передать делегат конфигурации методу Configure, так как создать службу сложнее. Создание типа эквивалентно тому, что делает платформа при вызове метода Configure. При вызове метода Configure регистрируется временный универсальный интерфейс IConfigureNamedOptions<TOptions>, имеющий конструктор, который принимает указанные универсальные типы службы.

Проверка параметров

Проверка параметров позволяет проверять значения параметров.

Рассмотрим следующий файл appsettings.json :

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

Следующий класс привязывается к разделу конфигурации "MyConfig" и применяет несколько правил DataAnnotations:

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

В приведенном ниже коде

  • вызывает AddOptions, чтобы получить класс OptionsBuilder<TOptions>, который привязывается к классу MyConfigOptions;
  • вызывает ValidateDataAnnotations для включения проверки с помощью DataAnnotations.
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<MyConfigOptions>()
            .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

        services.AddControllersWithViews();
    }

Метод расширения ValidateDataAnnotations определен в пакете NuGet Microsoft.Extensions.Options.DataAnnotations. Для веб-приложений, использующих пакет SDK Microsoft.NET.Sdk.Web, ссылка на этот пакет указывается неявным образом из общей платформы.

Следующий код отображает значения конфигурации или ошибки проверки:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;
           
        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
             msg = $"Key1: {_config.Value.Key1} \n" +
                   $"Key2: {_config.Value.Key2} \n" +
                   $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

Следующий код применяет более сложное правило проверки с использованием делегата:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<MyConfigOptions>()
        .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
        .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

    services.AddControllersWithViews();
}

IValidateOptions для сложной проверки

Следующий класс реализует IValidateOptions<TOptions>:

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions позволяет переместить код проверки из StartUp в класс.

С использованием приведенного выше кода проверка включается в Startup.ConfigureServices с помощью следующего кода:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

Пост-конфигурация параметров

Задайте пост-конфигурацию с помощью IPostConfigureOptions<TOptions>. Пост-конфигурация применяется после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки именованных параметров доступен метод PostConfigure.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки всех экземпляров конфигурации служит метод PostConfigureAll.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Доступ к параметрам во время запуска

IOptions<TOptions> и IOptionsMonitor<TOptions> можно использовать в методе Startup.Configure, так как службы создаются до выполнения метода Configure.

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Не используйте IOptions<TOptions> или IOptionsMonitor<TOptions> в Startup.ConfigureServices. Из-за очередности регистрации служб состояние параметров может быть несогласованным.

Пакет NuGet Options.ConfigurationExtensions

Пакет Microsoft.Extensions.Options.ConfigurationExtensions неявно упоминается в приложениях ASP.NET Core.

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

В параметрах также предусмотрен механизм для проверки данных конфигурации. Дополнительные сведения см. в разделе Проверка параметров.

Просмотреть или скачать образец кода (как скачивать)

Предварительные требования

Добавьте ссылку на метапакет Microsoft.AspNetCore.App или добавьте ссылку на пакет в пакет Microsoft.Extensions.Options.ConfigurationExtensions.

Интерфейсы параметров

IOptionsMonitor<TOptions> используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров TOptions. IOptionsMonitor<TOptions> поддерживается в следующих сценариях:

Сценарии пост-конфигурации позволяют задать или изменить параметры после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

Интерфейс IOptionsFactory<TOptions> отвечает за создание экземпляров параметров. Он имеет единственный метод Create. При реализации по умолчанию принимаются все зарегистрированные интерфейсы IConfigureOptions<TOptions> и IPostConfigureOptions<TOptions>. Также сначала выполняются все основные настройки, а затем действия после конфигурации. Она различает интерфейсы IConfigureNamedOptions<TOptions> и IConfigureOptions<TOptions> и вызывает только соответствующий интерфейс.

IOptionsMonitorCache<TOptions> используется интерфейсом IOptionsMonitor<TOptions> для записи экземпляров TOptions в кэш. IOptionsMonitorCache<TOptions> делает экземпляры параметров в мониторе недействительными, что приводит к повторному вычислению значений (TryRemove). Значения можно также вводить вручную с помощью TryAdd. Метод Clear используется, если необходимо повторно создать все именованные экземпляры по требованию.

IOptionsSnapshot<TOptions> полезно использовать в сценариях, когда параметры должны заново вычисляться при каждом запросе. Дополнительные сведения см. в разделе Повторная загрузка данных конфигурации с помощью IOptionsSnapshot.

IOptions<TOptions> можно использовать, чтобы обеспечить поддержку определенных параметров. Но IOptions<TOptions> не поддерживается в описанных выше сценариях IOptionsMonitor<TOptions>. Вы можете продолжать использовать IOptions<TOptions> в существующих платформах и библиотеках, которые уже используют интерфейс IOptions<TOptions>. В таком случае вам не потребуются сценарии, предоставляемые IOptionsMonitor<TOptions>.

Конфигурация общих параметров

Конфигурация общих параметров демонстрируется в примере 1 в примере приложения.

Класс параметров должен быть неабстрактным с открытым конструктором без параметров. Приведенный ниже класс (MyOptions) имеет два свойства: Option1 и Option2. Задавать значения по умолчанию необязательно, однако конструктор класса в этом примере задает значение по умолчанию для Option1. Значение по умолчанию для Option2 устанавливается путем прямой инициализации (Models/MyOptions.cs).

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

Класс MyOptions добавляется в контейнер службы с помощью интерфейса Configure и привязывается к конфигурации.

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

В следующей страничной модели применяется внедрение зависимостей конструктора с помощью IOptionsMonitor<TOptions> для доступа к параметрам (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

В файле appsettings.json примера задаются значения option1 и option2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Когда приложение выполняется, метод OnGet страничной модели возвращает строку со значениями класса параметров:

option1 = value1_from_json, option2 = -1

Примечание

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

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

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

Настройка простых параметров с помощью делегата

Настройка простых параметров с помощью делегата демонстрируется в примере 2 в примере приложения.

Используйте делегат для задания значений параметров. В образце приложения используется класс MyOptionsWithDelegateConfig (Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

В приведенном ниже коде в контейнер службы добавляется еще одна служба IConfigureOptions<TOptions>. Она использует делегат для настройки привязки к MyOptionsWithDelegateConfig:

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

Можно добавить несколько поставщиков конфигурации. Поставщики конфигурации доступны в пакетах NuGet и применяются в порядке регистрации. Для получения дополнительной информации см. Конфигурация в .NET Core.

При каждом вызове Configure служба IConfigureOptions<TOptions> добавляется в контейнер службы. В предыдущем примере значения Option1 и Option2 задаются в файле appsettings.json , однако значения Option1 и Option2 переопределяются настроенным делегатом.

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

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

Конфигурация подпараметров

Конфигурация подпараметров демонстрируется в примере 3 в примере приложения.

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

При привязке параметров к конфигурации каждое свойство, относящееся к типу параметров, привязывается к ключу конфигурации в формате property[:sub-property:]. Например, свойство MyOptions.Option1 привязывается к ключу Option1, который считывается из свойства option1 в файле appsettings.json .

В приведенном ниже коде в контейнер службы добавляется третья служба IConfigureOptions<TOptions>. Он привязывает MySubOptions к разделу subsection файла appsettings.json :

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

Методу GetSection нужно пространство имен Microsoft.Extensions.Configuration.

В файле appsettings.json примера определяется член subsection с ключами для suboption1 и suboption2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Класс MySubOptions определяет свойства SubOption1 и SubOption2 для хранения значений параметров (Models/MySubOptions.cs):

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

Метод OnGet страничной модели возвращает строку со значениями параметров (Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

Когда приложение выполняется, метод OnGet возвращает строку со значениями класса подпараметров:

subOption1 = subvalue1_from_json, subOption2 = 200

Внедрение параметров

Внедрение параметров демонстрируется в примере 4 в примере приложения.

Внедрите IOptionsMonitor<TOptions> в:

  • Страница Razor или представление MVC с директивой @inject Razor.
  • Модель страницы или представления.

Следующий пример из примера приложения внедряет IOptionsMonitor<TOptions> в модель страницы (Pages/index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

В примере приложения показано, как внедрить IOptionsMonitor<MyOptions> с директивой @inject.

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

Когда запускается приложение, значения параметров появляются на отображаемой странице:

Значения параметров Option1: value1_from_json и Option2: –1 загружаются из модели и путем внедрения в представление.

Повторная загрузка данных конфигурации с помощью IOptionsSnapshot

Повторная загрузка данных конфигурации с помощью IOptionsSnapshot<TOptions> демонстрируется в примере 5 в примере приложения.

С помощью IOptionsSnapshot<TOptions> параметры вычисляются один раз на каждый запрос при обращении к ним и кэшируются на все время существования запроса.

Разница между IOptionsMonitor и IOptionsSnapshot:

  • IOptionsMonitor — это одноэлементная служба, которая получает текущие значения параметров в любое время, что особенно полезно в одноэлементных зависимостях.
  • IOptionsSnapshot — это служба с заданной областью действия, предоставляющая моментальный снимок параметров на момент создания объекта IOptionsSnapshot<T>. Моментальные снимки параметров предназначены для использования с временными зависимостями и зависимостями с заданной областью действия.

В приведенном ниже примере демонстрируется создание экземпляра IOptionsSnapshot<TOptions> после изменения файла appsettings.json (Pages/Index.cshtml.cs). Несколько запросов к серверу возвращают константные значения, предоставляемые файлом appsettings.json , пока файл не изменится и конфигурация не загрузится повторно.

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

Ниже показаны начальные значения option1 и option2, загруженные из файла appsettings.json :

snapshot option1 = value1_from_json, snapshot option2 = -1

Измените значения в файле appsettings.json на value1_from_json UPDATED и 200. Сохраните файл appsettings.json. Обновите окно браузера, чтобы увидеть обновленные значения параметров:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

Поддержка именованных параметров в IConfigureNamedOptions

Включение поддержки именованных параметров в IConfigureNamedOptions<TOptions> демонстрируется в примере 6 в примере приложения.

Поддержка именованных параметров позволяет приложению различать конфигурации именованных параметров. В примере приложения именованные параметры должны быть объявлены с помощью элемента OptionsServiceCollectionExtensions.Configure, который вызывает метод расширения ConfigureNamedOptions<TOptions>.Configure. Именованные параметры вводятся с учетом регистра.

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

Пример приложения обращается к именованным параметрам с помощью метода Get (Pages/Index.cshtml.cs):

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

При запуске образца приложения возвращаются именованные параметры:

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

Значения named_options_1 предоставляются из конфигурации, которая загружается из файла appsettings.json . Значения named_options_2 предоставляются следующим образом:

  • Делегатом named_options_2 в ConfigureServices для Option1.
  • Значение по умолчанию для Option2 предоставляется классом MyOptions.

Настройка всех параметров с помощью метода ConfigureAll

Настройте все экземпляры параметров с помощью метода ConfigureAll. Приведенный ниже код настраивает Option1 для всех экземпляров конфигурации с общим значением. Добавьте следующий код в метод Startup.ConfigureServices вручную:

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

Если запустить образец приложения после добавления, результат будет следующим:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

Примечание

Все параметры являются именованными экземплярами. Существующие экземпляры IConfigureOptions<TOptions> считаются нацеленными на экземпляр Options.DefaultName, который имеет значение string.Empty. Интерфейс IConfigureNamedOptions<TOptions> также реализует интерфейс IConfigureOptions<TOptions>. Реализация IOptionsFactory<TOptions> по умолчанию содержит логику для надлежащего использования каждого экземпляра. Именованный параметр null предназначен для всех именованных экземпляров, а не для какого-то определенного (ConfigureAll и PostConfigureAll следуют этому соглашению).

API OptionsBuilder

OptionsBuilder<TOptions> используется для настройки экземпляров TOptions. OptionsBuilder упрощает создание именованных параметров, так как он является единственным параметром для первоначального вызова AddOptions<TOptions>(string optionsName) и не должен появляться во всех последующих вызовах. Проверка параметров и перегрузки ConfigureOptions, принимающие зависимости службы, доступны только через OptionsBuilder.

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

Использование служб внедрения зависимостей для настройки параметров

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

  • Передайте делегат конфигурации методу Configure в OptionsBuilder<TOptions>. OptionsBuilder<TOptions> предоставляет перегрузки метода Configure, которые позволяют использовать до пяти служб для настройки параметров:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Создайте собственный тип, реализующий IConfigureOptions<TOptions> или IConfigureNamedOptions<TOptions>, и зарегистрируйте этот тип как службу.

Рекомендуем передать делегат конфигурации методу Configure, так как создать службу сложнее. Создание собственного типа эквивалентно тому, что делает платформа при использовании метода Configure. При вызове метода Configure регистрируется временный универсальный интерфейс IConfigureNamedOptions<TOptions>, имеющий конструктор, который принимает указанные универсальные типы службы.

Проверка параметров

Эта функция позволяет проверить параметры при их настройке. Вызовите Validate с использованием метода проверки, который возвращает true, если параметры являются допустимыми, и false, если они являются недопустимыми:

// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
    .Configure(o => { }) // Configure the options
    .Validate(o => YourValidationShouldReturnTrueIfValid(o), 
        "custom error");

// Consumption
var monitor = services.BuildServiceProvider()
    .GetService<IOptionsMonitor<MyOptions>>();

try
{
    var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e) 
{
   // e.OptionsName returns "optionalOptionsName"
   // e.OptionsType returns typeof(MyOptions)
   // e.Failures returns a list of errors, which would contain 
   //     "custom error"
}

В предыдущем примере для именованного экземпляра параметров задается optionalOptionsName. Экземпляр параметров по умолчанию — Options.DefaultName.

Проверка выполняется при создании экземпляра параметров. Экземпляр параметров гарантированно проходит проверку при первом обращении.

Важно!

Проверка параметров не защищает от изменений, вносимых после создания экземпляра параметров. Например, параметры IOptionsSnapshot создаются и проверяются один раз для каждого запроса при первом обращении к параметрам. Параметры IOptionsSnapshot не проверяются повторно при последующих попытках доступа для этого же запроса.

Метод Validate принимает Func<TOptions, bool>. Чтобы полностью настроить проверку, реализуйте IValidateOptions<TOptions>, чтобы:

  • выполнить проверку нескольких типов параметров — class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>;
  • выполнить проверку, зависящую от другого типа параметра — public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options).

IValidateOptions проверяет:

  • определенный именованный экземпляр параметров;
  • все параметры, при которых name имеет значение null.

Возвращает ValidateOptionsResult из реализации интерфейса:

public interface IValidateOptions<TOptions> where TOptions : class
{
    ValidateOptionsResult Validate(string name, TOptions options);
}

Проверка на основе заметок к данным доступна в пакете Microsoft.Extensions.Options.DataAnnotations с помощью вызова метода ValidateDataAnnotations в OptionsBuilder<TOptions>. Microsoft.Extensions.Options.DataAnnotations входит в состав метапакета Microsoft.AspNetCore.App.

using Microsoft.Extensions.DependencyInjection;

private class AnnotatedOptions
{
    [Required]
    public string Required { get; set; }

    [StringLength(5, ErrorMessage = "Too long.")]
    public string StringLength { get; set; }

    [Range(-5, 5, ErrorMessage = "Out of range.")]
    public int IntRange { get; set; }
}

[Fact]
public void CanValidateDataAnnotations()
{
    var services = new ServiceCollection();
    services.AddOptions<AnnotatedOptions>()
        .Configure(o =>
        {
            o.StringLength = "111111";
            o.IntRange = 10;
            o.Custom = "nowhere";
        })
        .ValidateDataAnnotations();

    var sp = services.BuildServiceProvider();

    var error = Assert.Throws<OptionsValidationException>(() => 
        sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().CurrentValue);
    ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1,
        "DataAnnotation validation failed for members Required " +
            "with the error 'The Required field is required.'.",
        "DataAnnotation validation failed for members StringLength " +
            "with the error 'Too long.'.",
        "DataAnnotation validation failed for members IntRange " +
            "with the error 'Out of range.'.");
}

Безотложная проверка (быстрое прекращение при запуске) находится на этапе рассмотрения для включения в будущие выпуски.

Пост-конфигурация параметров

Задайте пост-конфигурацию с помощью IPostConfigureOptions<TOptions>. Пост-конфигурация применяется после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки именованных параметров доступен метод PostConfigure.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки всех экземпляров конфигурации служит метод PostConfigureAll.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Доступ к параметрам во время запуска

IOptions<TOptions> и IOptionsMonitor<TOptions> можно использовать в методе Startup.Configure, так как службы создаются до выполнения метода Configure.

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Не используйте IOptions<TOptions> или IOptionsMonitor<TOptions> в Startup.ConfigureServices. Из-за очередности регистрации служб состояние параметров может быть несогласованным.

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

В параметрах также предусмотрен механизм для проверки данных конфигурации. Дополнительные сведения см. в разделе Проверка параметров.

Просмотреть или скачать образец кода (как скачивать)

Предварительные требования

Добавьте ссылку на метапакет Microsoft.AspNetCore.App или добавьте ссылку на пакет в пакет Microsoft.Extensions.Options.ConfigurationExtensions.

Интерфейсы параметров

IOptionsMonitor<TOptions> используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров TOptions. IOptionsMonitor<TOptions> поддерживается в следующих сценариях:

Сценарии пост-конфигурации позволяют задать или изменить параметры после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

Интерфейс IOptionsFactory<TOptions> отвечает за создание экземпляров параметров. Он имеет единственный метод Create. При реализации по умолчанию принимаются все зарегистрированные интерфейсы IConfigureOptions<TOptions> и IPostConfigureOptions<TOptions>. Также сначала выполняются все основные настройки, а затем действия после конфигурации. Она различает интерфейсы IConfigureNamedOptions<TOptions> и IConfigureOptions<TOptions> и вызывает только соответствующий интерфейс.

IOptionsMonitorCache<TOptions> используется интерфейсом IOptionsMonitor<TOptions> для записи экземпляров TOptions в кэш. IOptionsMonitorCache<TOptions> делает экземпляры параметров в мониторе недействительными, что приводит к повторному вычислению значений (TryRemove). Значения можно также вводить вручную с помощью TryAdd. Метод Clear используется, если необходимо повторно создать все именованные экземпляры по требованию.

IOptionsSnapshot<TOptions> полезно использовать в сценариях, когда параметры должны заново вычисляться при каждом запросе. Дополнительные сведения см. в разделе Повторная загрузка данных конфигурации с помощью IOptionsSnapshot.

IOptions<TOptions> можно использовать, чтобы обеспечить поддержку определенных параметров. Но IOptions<TOptions> не поддерживается в описанных выше сценариях IOptionsMonitor<TOptions>. Вы можете продолжать использовать IOptions<TOptions> в существующих платформах и библиотеках, которые уже используют интерфейс IOptions<TOptions>. В таком случае вам не потребуются сценарии, предоставляемые IOptionsMonitor<TOptions>.

Конфигурация общих параметров

Конфигурация общих параметров демонстрируется в примере 1 в примере приложения.

Класс параметров должен быть неабстрактным с открытым конструктором без параметров. Приведенный ниже класс (MyOptions) имеет два свойства: Option1 и Option2. Задавать значения по умолчанию необязательно, однако конструктор класса в этом примере задает значение по умолчанию для Option1. Значение по умолчанию для Option2 устанавливается путем прямой инициализации (Models/MyOptions.cs).

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

Класс MyOptions добавляется в контейнер службы с помощью интерфейса Configure и привязывается к конфигурации.

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

В следующей страничной модели применяется внедрение зависимостей конструктора с помощью IOptionsMonitor<TOptions> для доступа к параметрам (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

В файле appsettings.json примера задаются значения option1 и option2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Когда приложение выполняется, метод OnGet страничной модели возвращает строку со значениями класса параметров:

option1 = value1_from_json, option2 = -1

Примечание

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

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

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

Настройка простых параметров с помощью делегата

Настройка простых параметров с помощью делегата демонстрируется в примере 2 в примере приложения.

Используйте делегат для задания значений параметров. В образце приложения используется класс MyOptionsWithDelegateConfig (Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

В приведенном ниже коде в контейнер службы добавляется еще одна служба IConfigureOptions<TOptions>. Она использует делегат для настройки привязки к MyOptionsWithDelegateConfig:

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

Можно добавить несколько поставщиков конфигурации. Поставщики конфигурации доступны в пакетах NuGet и применяются в порядке регистрации. Для получения дополнительной информации см. Конфигурация в .NET Core.

При каждом вызове Configure служба IConfigureOptions<TOptions> добавляется в контейнер службы. В предыдущем примере значения Option1 и Option2 задаются в файле appsettings.json , однако значения Option1 и Option2 переопределяются настроенным делегатом.

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

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

Конфигурация подпараметров

Конфигурация подпараметров демонстрируется в примере 3 в примере приложения.

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

При привязке параметров к конфигурации каждое свойство, относящееся к типу параметров, привязывается к ключу конфигурации в формате property[:sub-property:]. Например, свойство MyOptions.Option1 привязывается к ключу Option1, который считывается из свойства option1 в файле appsettings.json .

В приведенном ниже коде в контейнер службы добавляется третья служба IConfigureOptions<TOptions>. Он привязывает MySubOptions к разделу subsection файла appsettings.json :

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

Методу GetSection нужно пространство имен Microsoft.Extensions.Configuration.

В файле appsettings.json примера определяется член subsection с ключами для suboption1 и suboption2:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Класс MySubOptions определяет свойства SubOption1 и SubOption2 для хранения значений параметров (Models/MySubOptions.cs):

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

Метод OnGet страничной модели возвращает строку со значениями параметров (Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

Когда приложение выполняется, метод OnGet возвращает строку со значениями класса подпараметров:

subOption1 = subvalue1_from_json, subOption2 = 200

Параметры, предоставляемые моделью представления или посредством прямого внедрения представления

Параметры, предоставляемые моделью представления или посредством прямого внедрения представления, демонстрируются в примере 4 в примере приложения.

Параметры могут передаваться в модель представления или путем внедрения IOptionsMonitor<TOptions> непосредственно в представление (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

В примере приложения показано, как внедрить IOptionsMonitor<MyOptions> с директивой @inject.

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

Когда запускается приложение, значения параметров появляются на отображаемой странице:

Значения параметров Option1: value1_from_json и Option2: –1 загружаются из модели и путем внедрения в представление.

Повторная загрузка данных конфигурации с помощью IOptionsSnapshot

Повторная загрузка данных конфигурации с помощью IOptionsSnapshot<TOptions> демонстрируется в примере 5 в примере приложения.

Интерфейс IOptionsSnapshot<TOptions> поддерживает повторную загрузку параметров с минимальными затратами на обработку.

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

В приведенном ниже примере демонстрируется создание экземпляра IOptionsSnapshot<TOptions> после изменения файла appsettings.json (Pages/Index.cshtml.cs). Несколько запросов к серверу возвращают константные значения, предоставляемые файлом appsettings.json , пока файл не изменится и конфигурация не загрузится повторно.

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

Ниже показаны начальные значения option1 и option2, загруженные из файла appsettings.json :

snapshot option1 = value1_from_json, snapshot option2 = -1

Измените значения в файле appsettings.json на value1_from_json UPDATED и 200. Сохраните файл appsettings.json. Обновите окно браузера, чтобы увидеть обновленные значения параметров:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

Поддержка именованных параметров в IConfigureNamedOptions

Включение поддержки именованных параметров в IConfigureNamedOptions<TOptions> демонстрируется в примере 6 в примере приложения.

Поддержка именованных параметров позволяет приложению различать конфигурации именованных параметров. В примере приложения именованные параметры должны быть объявлены с помощью элемента OptionsServiceCollectionExtensions.Configure, который вызывает метод расширения ConfigureNamedOptions<TOptions>.Configure. Именованные параметры вводятся с учетом регистра.

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

Пример приложения обращается к именованным параметрам с помощью метода Get (Pages/Index.cshtml.cs):

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

При запуске образца приложения возвращаются именованные параметры:

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

Значения named_options_1 предоставляются из конфигурации, которая загружается из файла appsettings.json . Значения named_options_2 предоставляются следующим образом:

  • Делегатом named_options_2 в ConfigureServices для Option1.
  • Значение по умолчанию для Option2 предоставляется классом MyOptions.

Настройка всех параметров с помощью метода ConfigureAll

Настройте все экземпляры параметров с помощью метода ConfigureAll. Приведенный ниже код настраивает Option1 для всех экземпляров конфигурации с общим значением. Добавьте следующий код в метод Startup.ConfigureServices вручную:

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

Если запустить образец приложения после добавления, результат будет следующим:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

Примечание

Все параметры являются именованными экземплярами. Существующие экземпляры IConfigureOptions<TOptions> считаются нацеленными на экземпляр Options.DefaultName, который имеет значение string.Empty. Интерфейс IConfigureNamedOptions<TOptions> также реализует интерфейс IConfigureOptions<TOptions>. Реализация IOptionsFactory<TOptions> по умолчанию содержит логику для надлежащего использования каждого экземпляра. Именованный параметр null предназначен для всех именованных экземпляров, а не для какого-то определенного (ConfigureAll и PostConfigureAll следуют этому соглашению).

API OptionsBuilder

OptionsBuilder<TOptions> используется для настройки экземпляров TOptions. OptionsBuilder упрощает создание именованных параметров, так как он является единственным параметром для первоначального вызова AddOptions<TOptions>(string optionsName) и не должен появляться во всех последующих вызовах. Проверка параметров и перегрузки ConfigureOptions, принимающие зависимости службы, доступны только через OptionsBuilder.

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

Использование служб внедрения зависимостей для настройки параметров

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

  • Передайте делегат конфигурации методу Configure в OptionsBuilder<TOptions>. OptionsBuilder<TOptions> предоставляет перегрузки метода Configure, которые позволяют использовать до пяти служб для настройки параметров:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Создайте собственный тип, реализующий IConfigureOptions<TOptions> или IConfigureNamedOptions<TOptions>, и зарегистрируйте этот тип как службу.

Рекомендуем передать делегат конфигурации методу Configure, так как создать службу сложнее. Создание собственного типа эквивалентно тому, что делает платформа при использовании метода Configure. При вызове метода Configure регистрируется временный универсальный интерфейс IConfigureNamedOptions<TOptions>, имеющий конструктор, который принимает указанные универсальные типы службы.

Пост-конфигурация параметров

Задайте пост-конфигурацию с помощью IPostConfigureOptions<TOptions>. Пост-конфигурация применяется после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки именованных параметров доступен метод PostConfigure.

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Для последующей настройки всех экземпляров конфигурации служит метод PostConfigureAll.

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Доступ к параметрам во время запуска

IOptions<TOptions> и IOptionsMonitor<TOptions> можно использовать в методе Startup.Configure, так как службы создаются до выполнения метода Configure.

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Не используйте IOptions<TOptions> или IOptionsMonitor<TOptions> в Startup.ConfigureServices. Из-за очередности регистрации служб состояние параметров может быть несогласованным.

Дополнительные ресурсы