Шаблон параметров в ASP.NET Core
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
Шаблон параметров использует классы для обеспечения строго типизированного доступа к группам связанных параметров. Когда параметры конфигурации изолируются по сценарию в отдельных классах, в приложениях соблюдаются два важных принципа программной инженерии.
- Инкапсуляция:
- Классы, которые зависят от параметров конфигурации, зависят только от используемых ими параметров конфигурации.
- Разделение проблем:
- Параметры для разных частей приложения не зависят друг от друга и не связаны друг с другом.
В параметрах также предусмотрен механизм для проверки данных конфигурации. Дополнительные сведения см. в разделе Проверка параметров.
В этой статье приводятся сведения о шаблоне параметров в ASP.NET Core. Сведения об использовании шаблона параметров в консольных приложениях см. в разделе Шаблон параметров в .NET.
Привязка иерархической конфигурации
Предпочтительный способ чтения связанных значений конфигурации — использование шаблона параметров. Например, чтобы считать следующие значения конфигурации:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Создайте следующий класс PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Класс параметров:
- Должен быть не абстрактным.
- Имеет общедоступные свойства записи для чтения и записи типа, имеющего соответствующие элементы в конфигурации, привязаны.
- Имеет свойства чтения и записи, связанные с соответствующими записями в конфигурации.
- Не привязаны его поля. В приведенном выше коде свойство
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, внесенные после запуска приложения, считываются.
Bind также позволяет сцепить абстрактный класс. Рассмотрим следующий код, использующий абстрактный класс SomethingWithAName
:
namespace ConfigSample.Options;
public abstract class SomethingWithAName
{
public abstract string? Name { get; set; }
}
public class NameTitleOptions(int age) : SomethingWithAName
{
public const string NameTitle = "NameTitle";
public override string? Name { get; set; }
public string Title { get; set; } = string.Empty;
public int Age { get; set; } = age;
}
В следующем коде NameTitleOptions
отображаются значения конфигурации:
public class Test33Model : PageModel
{
private readonly IConfiguration Configuration;
public Test33Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var nameTitleOptions = new NameTitleOptions(22);
Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
return Content($"Title: {nameTitleOptions.Title} \n" +
$"Name: {nameTitleOptions.Name} \n" +
$"Age: {nameTitleOptions.Age}"
);
}
}
Bind
Вызовы менее строги, чем вызовы:Get<>
Bind
позволяет сцепить абстрактное.Get<>
должен создать сам экземпляр.
Шаблон параметров
Альтернативный подход при использовании шаблона параметров — привязать раздел Position
и добавить его в контейнер службы внедрения зависимостей. В следующем коде PositionOptions
добавляется в контейнер службы с помощью интерфейса Configure и привязывается к конфигурации:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
С помощью приведенного выше кода следующий код считывает параметры расположения:
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.
Интерфейсы параметров
- Не поддерживается:
- чтение данных конфигурации после запуска приложения;
- именованные параметры;
- Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Полезно использовать в сценариях, когда параметры нужно заново вычислять при каждом запросе. Дополнительные сведения см. в разделе Использование IOptionsSnapshot для чтения обновленных данных.
- Регистрируется как Scoped (С областью), поэтому его нельзя внедрить в службу singleton.
- Поддерживает именованные параметры.
- Используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров
TOptions
. - Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Поддерживает:
- Уведомления об изменениях
- именованные параметры;
- перезагружаемые конфигурации;
- объявление определенных параметров недействительными (IOptionsMonitorCache<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>:
- Параметры вычисляются один раз на каждый запрос при обращении к ним и кэшируются на все время существования запроса.
- Производительность может значительно снизиться, так как это служба с заданной областью действия и для каждого запроса вычисление выполняется заново. Дополнительные сведения см. в описании этой проблемы на GitHub и в статье Повышение производительности привязки конфигурации.
- Изменения конфигурации считываются после запуска приложения при использовании поставщиков конфигурации, поддерживающих чтение обновленных значений конфигурации.
Разница между 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
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
В приведенном выше коде изменения в файле конфигурации JSON, внесенные после запуска приложения, считываются.
IOptionsMonitor
Следующий код регистрирует экземпляр конфигурации, к которому привязывается MyOptions
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
В следующем примере используется 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; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
Следующий код служит для настройки именованных параметров:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
Следующий код отображает именованные параметры:
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 , позволяющие использовать до пяти служб для настройки параметров:builder.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
.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
Метод расширения 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);
}
Следующий код применяет более сложное правило проверки с использованием делегата:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.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.
var app = builder.Build();
IValidateOptions<TOptions>
и IValidatableObject
.
Следующий класс реализует 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
позволяет переместить код проверки из Program.cs
в класс.
С использованием приведенного выше кода проверка включается в Program.cs
с помощью следующего кода:
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
Проверка параметров также поддерживает IValidatableObject. Для проверки на уровне класса внутри самого класса:
- Реализуйте интерфейс
IValidatableObject
и его метод Validate внутри класса. - Вызовите ValidateDataAnnotations в
Program.cs
.
ValidateOnStart
Проверка параметров выполняется при первом создании экземпляра TOption
. Это означает, например, что при первом доступе к IOptionsSnapshot<TOptions>.Value
конвейеру запросов или при IOptionsMonitor<TOptions>.Get(string)
вызове параметров присутствует. После перезагрузки параметров проверка выполняется снова. Среда выполнения ASP.NET Core используется OptionsCache<TOptions> для кэширования экземпляра параметров после его создания.
Для безотложной проверки при запуске приложения вызовите ValidateOnStart<TOptions>(OptionsBuilder<TOptions>) в Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Пост-конфигурация параметров
Задайте пост-конфигурацию с помощью IPostConfigureOptions<TOptions>. Пост-конфигурация применяется после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Для последующей настройки именованных параметров доступен метод PostConfigure.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
Для последующей настройки всех экземпляров конфигурации служит метод PostConfigureAll.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Параметры доступа в Program.cs
Чтобы получить доступ к IOptions<TOptions> или IOptionsMonitor<TOptions> в Program.cs
, вызовите GetRequiredService в WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Дополнительные ресурсы
Авторы: Кирк Ларкин (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; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Класс параметров:
- Должен быть неабстрактным с открытым конструктором без параметров.
- Все открытые свойства чтения и записи типа привязаны.
- Поля не привязаны. В приведенном выше коде свойство
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
добавляется в контейнер службы с помощью интерфейса Configure и привязывается к конфигурации:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
С помощью приведенного выше кода следующий код считывает параметры расположения:
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.
Интерфейсы параметров
- Не поддерживается:
- чтение данных конфигурации после запуска приложения;
- именованные параметры;
- Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Полезно использовать в сценариях, когда параметры нужно заново вычислять при каждом запросе. Дополнительные сведения см. в разделе Использование IOptionsSnapshot для чтения обновленных данных.
- Регистрируется как Scoped (С областью), поэтому его нельзя внедрить в службу singleton.
- Поддерживает именованные параметры.
- Используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров
TOptions
. - Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Поддерживает:
- Уведомления об изменениях
- именованные параметры;
- перезагружаемые конфигурации;
- объявление определенных параметров недействительными (IOptionsMonitorCache<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>:
- Параметры вычисляются один раз на каждый запрос при обращении к ним и кэшируются на все время существования запроса.
- Производительность может значительно снизиться, так как это служба с заданной областью действия и для каждого запроса вычисление выполняется заново. Дополнительные сведения см. в описании этой проблемы на GitHub и в статье Повышение производительности привязки конфигурации.
- Изменения конфигурации считываются после запуска приложения при использовании поставщиков конфигурации, поддерживающих чтение обновленных значений конфигурации.
Разница между 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
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
В приведенном выше коде изменения в файле конфигурации JSON, внесенные после запуска приложения, считываются.
IOptionsMonitor
Следующий код регистрирует экземпляр конфигурации, к которому привязывается MyOptions
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
В следующем примере используется 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; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
Следующий код служит для настройки именованных параметров:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
Следующий код отображает именованные параметры:
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 , позволяющие использовать до пяти служб для настройки параметров:builder.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
.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
Метод расширения 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);
}
Следующий код применяет более сложное правило проверки с использованием делегата:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.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.
var app = builder.Build();
IValidateOptions<TOptions>
и IValidatableObject
.
Следующий класс реализует 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
позволяет переместить код проверки из Program.cs
в класс.
С использованием приведенного выше кода проверка включается в Program.cs
с помощью следующего кода:
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
Проверка параметров также поддерживает IValidatableObject. Для проверки на уровне класса внутри самого класса:
- Реализуйте интерфейс
IValidatableObject
и его метод Validate внутри класса. - Вызовите ValidateDataAnnotations в
Program.cs
.
ValidateOnStart
Проверка параметров выполняется при первом создании реализации IOptions<TOptions>, IOptionsSnapshot<TOptions> или IOptionsMonitor<TOptions>. Для безотложной проверки при запуске приложения вызовите ValidateOnStart в Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Пост-конфигурация параметров
Задайте пост-конфигурацию с помощью IPostConfigureOptions<TOptions>. Пост-конфигурация применяется после выполнения всех действий настройки с помощью IConfigureOptions<TOptions>.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Для последующей настройки именованных параметров доступен метод PostConfigure.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
Для последующей настройки всех экземпляров конфигурации служит метод PostConfigureAll.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Параметры доступа в Program.cs
Чтобы получить доступ к IOptions<TOptions> или IOptionsMonitor<TOptions> в Program.cs
, вызовите GetRequiredService в WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Дополнительные ресурсы
Авторы: Кирк Ларкин (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
добавляется в контейнер службы с помощью интерфейса 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.
Интерфейсы параметров
- Не поддерживается:
- чтение данных конфигурации после запуска приложения;
- именованные параметры;
- Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Полезно использовать в сценариях, когда параметры нужно заново вычислять при каждом запросе. Дополнительные сведения см. в разделе Использование IOptionsSnapshot для чтения обновленных данных.
- Регистрируется как Scoped (с областью), поэтому его нельзя внедрить в службу singleton.
- Поддерживает именованные параметры.
- Используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров
TOptions
. - Регистрируется в качестве элемента singleton и может быть внедрен в любое время существования службы.
- Поддерживает:
- Уведомления об изменениях
- именованные параметры;
- перезагружаемые конфигурации;
- объявление определенных параметров недействительными (IOptionsMonitorCache<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.
ASP.NET Core