Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Note
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Warning
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
Шаблон параметров использует классы для обеспечения строго типизированного доступа к группам связанных параметров. Когда параметры конфигурации изолируются по сценарию в отдельных классах, в приложениях соблюдаются два важных принципа программной инженерии.
- Инкапсуляция. Классы, зависящие от параметров конфигурации, зависят только от используемых параметров конфигурации.
- Разделение проблем. Параметры для разных частей приложения не зависят или связаны друг с другом.
Параметры также предоставляют механизм для проверки данных конфигурации, описанных в разделе "Параметры проверки ".
В этой статье приводятся сведения о шаблоне параметров в ASP.NET Core. Сведения об использовании шаблона параметров в консольных приложениях см. в разделе Шаблон параметров в .NET.
Примеры в этой статье основаны на общем понимании внедрения служб в классы. Дополнительные сведения см. в статье Внедрение зависимостей в ASP.NET Core. Примеры основаны на BlazorRazor компонентах. Сведения о Razor примерах страниц см. в версии 7.0 этой статьи. В примерах в .NET 8 или более поздних версиях этой статьи используются основные конструкторы (первичные конструкторы (руководство по C#) и ссылочные типы, допускающие значение NULL (NRTs) с статическим анализом состояния .NET.
Использование шаблона параметров
Рассмотрим следующие данные конфигурации JSON из файла настроек приложения (например, appsettings.json), который содержит связанные данные об имени и должности сотрудника в должности в организации.
"Position": {
"Name": "Joe Smith",
"Title": "Editor"
}
PositionOptions Следующий класс параметров:
- Представляет собой POCO, простой класс .NET со свойствами. Класс options не должен быть абстрактным классом.
- Имеет общедоступные свойства для чтения и записи, которые соответствуют записям в данных конфигурации.
- Поле () . Поле
Positionиспользуется для предотвращения жесткой привязки строки"Position"в приложении при привязке класса к поставщику конфигурации.
PositionOptions.cs:
public class PositionOptions
{
public const string Position = "Position";
public string? Name { get; set; }
public string? Title { get; set; }
}
Следующий пример:
- Вызывает ConfigurationBinder.Bind, чтобы привязать класс
PositionOptionsк разделуPosition. - Отображает данные конфигурации
Position.
BasicOptions.razor:
@page "/basic-options"
@inject IConfiguration Config
Name: @positionOptions?.Name<br>
Title: @positionOptions?.Title
@code {
private PositionOptions? positionOptions;
protected override void OnInitialized()
{
positionOptions = new PositionOptions();
Config.GetSection(PositionOptions.Position).Bind(positionOptions);
}
}
BasicOptions.cshtml:
@page
@model RazorPagesSample.Pages.BasicOptionsModel
@{
}
BasicOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesSample.Pages;
public class BasicOptionsModel : PageModel
{
private readonly IConfiguration _config;
public BasicOptionsModel(IConfiguration config)
{
_config = config;
}
public ContentResult OnGet()
{
var positionOptions = new PositionOptions();
_config.GetSection(PositionOptions.Position).Bind(positionOptions);
return Content(
$"Name: {positionOptions.Name}\n" +
$"Title: {positionOptions.Title}");
}
}
Выходные данные:
Name: Joe Smith
Title: Editor
После запуска приложения считываются изменения конфигурации JSON в файле параметров приложения. Чтобы продемонстрировать поведение, измените одно или оба значения конфигурации в файле параметров приложения и перезагрузите страницу без перезапуска приложения.
Bind позволяет создать экземпляр абстрактного класса. Рассмотрим следующий пример, использующий абстрактный класс AbstractClassWithName.
NameTitleOptions.cs:
public abstract class AbstractClassWithName
{
public abstract string? Name { get; set; }
}
public class NameTitleOptions(int age) : AbstractClassWithName
{
public const string NameTitle = "NameTitle";
public override string? Name { get; set; }
public string? Title { get; set; }
public int Age { get; set; } = age;
}
Конфигурация JSON:
"NameTitle": {
"Name": "Sally Jones",
"Title": "Writer"
}
В следующем примере отображаются конфигурационные значения NameTitleOptions.
AbstractClassOptions.razor:
@page "/abstract-class-options"
@inject IConfiguration Config
Name: @nameTitleOptions?.Name<br>
Title: @nameTitleOptions?.Title<br>
Age: @nameTitleOptions?.Age
@code {
private NameTitleOptions? nameTitleOptions;
protected override void OnInitialized()
{
nameTitleOptions = new NameTitleOptions(22);
Config.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
}
}
AbstractClassOptions.cshtml:
@page
@model RazorPagesSample.Pages.AbstractClassOptionsModel
@{
}
AbstractClassOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesSample.Pages;
public class AbstractClassOptionsModel : PageModel
{
private readonly IConfiguration _config;
public AbstractClassOptionsModel(IConfiguration config)
{
_config = config;
}
public ContentResult OnGet()
{
var nameTitleOptions = new NameTitleOptions(22);
_config.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
return Content(
$"Name: {nameTitleOptions.Name}\n" +
$"Title: {nameTitleOptions.Title}\n" +
$"Age: {nameTitleOptions.Age}");
}
}
Выходные данные:
Name: Sally Jones
Title: Writer
Age: 22
ConfigurationBinder.Get привязывает и возвращает указанный тип. В следующем примере показано, как использовать Get с классом PositionOptions .
GetOptions.razor:
@page "/get-options"
@inject IConfiguration Config
Name: @positionOptions?.Name<br>
Title: @positionOptions?.Title
@code {
private PositionOptions? positionOptions;
protected override void OnInitialized() =>
positionOptions = Config.GetSection(PositionOptions.Position)
.Get<PositionOptions>();
}
GetOptions.cshtml:
@page
@model RazorPagesSample.Pages.GetOptionsModel
@{
}
GetOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesSample.Pages;
public class GetOptionsModel : PageModel
{
private readonly IConfiguration _config;
public GetOptionsModel(IConfiguration config)
{
_config = config;
}
public ContentResult OnGet()
{
var positionOptions = _config.GetSection(PositionOptions.Position)
.Get<PositionOptions>();
return Content(
$"Name: {positionOptions?.Name}\n" +
$"Title: {positionOptions?.Title}");
}
}
Выходные данные:
Name: Joe Smith
Title: Editor
После запуска приложения считываются изменения конфигурации JSON в файле параметров приложения. Чтобы продемонстрировать поведение, измените одно или оба значения конфигурации в файле параметров приложения и перезагрузите страницу без перезапуска приложения.
Сводка различий между ConfigurationBinder.Bind и ConfigurationBinder.Get:
- Get обычно удобнее, чем использовать Bind , так как Get создает и возвращает новый экземпляр объекта, а Bind заполняет свойства существующего экземпляра объекта, который обычно устанавливается другой строкой кода.
- Bind позволяет инстанцировать абстрактный класс, но Get может создать только неабстрактный экземпляр типа параметров.
Привязка параметров к контейнеру службы внедрения зависимостей
В следующем примере PositionOptions и Configure добавляются в контейнер службы и привязаны к конфигурации.
Конфигурация JSON:
"Position": {
"Name": "Joe Smith",
"Title": "Editor"
}
PositionOptions.cs:
public class PositionOptions
{
public const string Position = "Position";
public string? Name { get; set; }
public string? Title { get; set; }
}
Где службы регистрируются для внедрения зависимостей в файле приложения Program :
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
В следующем примере считываются параметры позиции.
DIOptions.razor:
@page "/di-options"
@using Microsoft.Extensions.Options
@inject IOptions<PositionOptions> Options
Name: @Options.Value.Name<br>
Title: @Options.Value.Title
DIOptions.cshtml:
@page
@model RazorPagesSample.Pages.DIOptionsModel
@{
}
DIOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class DIOptionsModel : PageModel
{
private readonly IOptions<PositionOptions> _options;
public DIOptionsModel(IOptions<PositionOptions> options)
{
_options = options;
}
public ContentResult OnGet()
{
return Content(
$"Name: {_options.Value.Name}\n" +
$"Title: {_options.Value.Title}");
}
}
Выходные данные:
Name: Joe Smith
Title: Editor
В приведенном выше коде изменения конфигурации JSON в файле параметров приложения после запуска приложения не считываются. Чтобы прочитать изменения после запуска приложения, используйте IOptionsSnapshot.
Интерфейсы опций
-
Не поддерживается:
- чтение данных конфигурации после запуска приложения;
- Именованные параметры.
- Регистрируется в качестве единой службы и может быть внедрена в любое время существования службы.
- Далее в этой статье описано в разделе "Использование
IOptionsSnapshotдля чтения обновленных данных ". - Полезно использовать в сценариях, когда параметры нужно заново вычислять при каждом запросе.
- Регистрируется как служба с областью действия, поэтому ее нельзя внедрить в одну службу.
- Поддерживает именованные параметры.
- Далее в этой статье описано в разделе "Использование
IOptionsMonitorдля чтения обновленных данных ". - Используется для извлечения параметров и управления уведомлениями о параметрах для экземпляров
TOptions. - Регистрируется в качестве единой службы и может быть внедрена в любое время существования службы.
- Supports:
- Уведомления об изменениях.
- Именованные параметры.
- Перезагрузимая конфигурация.
- Выборочное аннулирование параметров (IOptionsMonitorCache<TOptions>).
Сценарии после настройки позволяют настраивать или изменять параметры после завершения настройки IConfigureOptions<TOptions> .
IOptionsFactory<TOptions> отвечает за создание новых экземпляров опций. Он имеет единственный метод Create. При реализации по умолчанию принимаются все зарегистрированные интерфейсы IConfigureOptions<TOptions> и IPostConfigureOptions<TOptions>. Также сначала выполняются все основные настройки, а затем действия после конфигурации. Она различает интерфейсы IConfigureNamedOptions<TOptions> и IConfigureOptions<TOptions> и вызывает только соответствующий интерфейс.
Использование IOptionsSnapshot для чтения обновленных данных
Использование IOptionsSnapshot<TOptions>:
- Параметры вычисляются один раз на каждый запрос при обращении к ним и кэшируются на все время существования запроса.
- Может привести к значительным потерям производительности, так как это служба с областью действия и пересчитывается для каждого запроса. Для получения дополнительной информации см.
IOptionsSnapshotо том, почему это очень медленно (dotnet/runtime#53793) и как улучшить производительность привязки конфигурации (dotnet/runtime#36130). - Изменения конфигурации считываются после запуска приложения при использовании поставщиков конфигурации, поддерживающих чтение обновленных значений конфигурации.
Разница между IOptionsMonitor и IOptionsSnapshot<TOptions>:
- IOptionsMonitor<TOptions> — это одноэлементная служба, которая получает текущие значения параметров в любое время, что особенно полезно в одноэлементных зависимостях.
-
IOptionsSnapshot<TOptions> — это служба с заданной областью действия, предоставляющая моментальный снимок параметров на момент создания объекта
IOptionsSnapshot<T>. Моментальные снимки параметров предназначены для использования с временными зависимостями и зависимостями с заданной областью действия.
Среда выполнения ASP.NET Core использует OptionsCache<TOptions> для кэширования экземпляра параметров после его создания.
Конфигурация JSON:
"Position": {
"Name": "Joe Smith",
"Title": "Editor"
}
PositionOptions.cs:
public class PositionOptions
{
public const string Position = "Position";
public string? Name { get; set; }
public string? Title { get; set; }
}
В следующем примере используется IOptionsSnapshot<TOptions>.
SnapshotOptions.razor:
@page "/snapshot-options"
@using Microsoft.Extensions.Options
@inject IOptionsSnapshot<PositionOptions> Options
Name: @Options.Value.Name<br>
Title: @Options.Value.Title
SnapshotOptions.cshtml:
@page
@model RazorPagesSample.Pages.SnapshotOptionsModel
@{
}
SnapshotOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class SnapshotOptionsModel : PageModel
{
private readonly IOptionsSnapshot<PositionOptions> _options;
public SnapshotOptionsModel(IOptionsSnapshot<PositionOptions> options)
{
_options = options;
}
public ContentResult OnGet()
{
return Content(
$"Name: {_options.Value.Name}\n" +
$"Title: {_options.Value.Title}");
}
}
Где службы регистрируются для внедрения зависимостей, в следующем примере регистрируется экземпляр конфигурации, PositionOptions который привязывается к:
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
Выходные данные:
Name: Joe Smith
Title: Editor
После запуска приложения считываются изменения конфигурации JSON в файле параметров приложения. Чтобы продемонстрировать поведение, измените одно или оба значения конфигурации в файле параметров приложения и перезагрузите страницу без перезапуска приложения.
Использование IOptionsMonitor для чтения обновленных данных
IOptionsMonitor<TOptions> используется для получения параметров и управления уведомлениями параметров для TOptions инстанций.
Разница между IOptionsMonitor<TOptions> и IOptionsSnapshot:
- IOptionsMonitor<TOptions> — это одноэлементная служба, которая получает текущие значения параметров в любое время, что особенно полезно в одноэлементных зависимостях.
-
IOptionsSnapshot<TOptions> — это служба с заданной областью действия, предоставляющая моментальный снимок параметров на момент создания объекта
IOptionsSnapshot<T>. Моментальные снимки параметров предназначены для использования с временными зависимостями и зависимостями с заданной областью действия.
IOptionsMonitorCache<TOptions> используется IOptionsMonitor<TOptions> для кеширования экземпляров TOptions.
IOptionsMonitorCache<TOptions>.TryRemove аннулирует экземпляры опций в мониторе, чтобы значение было пересчитано. Значения можно также вводить вручную с помощью IOptionsMonitorCache<TOptions>.TryAdd. Метод Clear используется, если необходимо повторно создать все именованные экземпляры по требованию.
Конфигурация JSON:
"Position": {
"Name": "Joe Smith",
"Title": "Editor"
}
PositionOptions.cs:
public class PositionOptions
{
public const string Position = "Position";
public string? Name { get; set; }
public string? Title { get; set; }
}
Где службы регистрируются для внедрения зависимостей, в следующем примере регистрируется экземпляр конфигурации, PositionOptions который привязывается к:
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
В следующем примере используется IOptionsMonitor<TOptions>.
MonitorOptions.razor:
@page "/monitor-options"
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<PositionOptions> Options
Name: @Options.CurrentValue.Name<br>
Title: @Options.CurrentValue.Title
MonitorOptions.cshtml:
@page
@model RazorPagesSample.Pages.MonitorOptionsModel
@{
}
MonitorOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class MonitorOptionsModel : PageModel
{
private readonly IOptionsMonitor<PositionOptions> _options;
public MonitorOptionsModel(IOptionsMonitor<PositionOptions> options)
{
_options = options;
}
public ContentResult OnGet()
{
return Content(
$"Name: {_options.CurrentValue.Name}\n" +
$"Title: {_options.CurrentValue.Title}");
}
}
Выходные данные:
Name: Joe Smith
Title: Editor
После запуска приложения считываются изменения конфигурации JSON в файле параметров приложения. Чтобы продемонстрировать поведение, измените одно или оба значения конфигурации в файле параметров приложения и перезагрузите страницу без перезапуска приложения.
Указание пользовательского имени ключа для свойства конфигурации с помощью ConfigurationKeyName
По умолчанию имена свойств класса options используются в качестве имени ключа в источнике конфигурации. Если имя свойства равно Title, то имя ключа в конфигурации также равно Title.
Когда имена различаются, вы можете использовать атрибут[ConfigurationKeyName] для указания имени ключа в источнике конфигурации. С помощью этого метода можно сопоставить свойство в конфигурации с свойством в коде с другим именем. Это полезно, если имя свойства в источнике конфигурации не является допустимым идентификатором C# или если вы хотите использовать другое имя в коде.
Например, рассмотрим следующий класс параметров.
PositionKeyName.cs:
public class PositionKeyName
{
public const string Position = "PositionKeyName";
[ConfigurationKeyName("PositionName")]
public string? Name { get; set; }
[ConfigurationKeyName("PositionTitle")]
public string? Title { get; set; }
}
Свойства класса Name и Title привязаны к PositionName и PositionTitle из следующей конфигурации JSON.
"PositionKeyName": {
"PositionName": "Carlos Diego",
"PositionTitle": "Director"
}
PositionKeyNameOptions.razor:
@page "/position-key-name-options"
@inject IConfiguration Config
Name: @positionOptions?.Name<br>
Title: @positionOptions?.Title
@code {
private PositionKeyName? positionOptions;
protected override void OnInitialized() =>
positionOptions = Config.GetSection(PositionKeyName.Position)
.Get<PositionKeyName>();
}
PositionKeyName.cshtml:
@page
@model RazorPagesSample.Pages.PositionKeyNameModel
@{
}
PositionKeyName.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesSample.Pages;
public class PositionKeyNameModel : PageModel
{
private readonly IConfiguration _config;
public PositionKeyNameModel(IConfiguration config)
{
_config = config;
}
public ContentResult OnGet()
{
var positionOptions = _config.GetSection(PositionKeyName.Position)
.Get<PositionKeyName>();
return Content(
$"Name: {positionOptions?.Name}\n" +
$"Title: {positionOptions?.Title}");
}
}
Выходные данные:
Name: Carlos Diego
Title: Director
После запуска приложения считываются изменения конфигурации JSON в файле параметров приложения. Чтобы продемонстрировать поведение, измените одно или оба значения конфигурации в файле параметров приложения и перезагрузите страницу без перезапуска приложения.
Поддержка именованных параметров с помощью IConfigureNamedOptions
Именованные параметры:
- полезны, если несколько разделов конфигурации привязываются к одним и тем же свойствам;
- используются с учетом регистра.
Рассмотрим следующую конфигурацию JSON:
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
Вместо создания двух классов для привязки TopItem:Month и TopItem:Yearдля каждого раздела используется следующий класс.
TopItemSettings.cs:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string? Name { get; set; }
public string? Model { get; set; }
}
Где службы регистрируются для внедрения зависимостей, в следующем примере настраиваются именованные параметры:
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
В следующем примере отображаются именованные параметры:
NamedOptions.razor:
@page "/named-options"
@using Microsoft.Extensions.Options
@inject IOptionsSnapshot<TopItemSettings> Options
Month: Name: @monthTopItem?.Name Model: @monthTopItem?.Model<br>
Year: Name: @yearTopItem?.Name Model: @yearTopItem?.Model
@code {
private TopItemSettings? monthTopItem;
private TopItemSettings? yearTopItem;
protected override void OnInitialized()
{
monthTopItem = Options.Get(TopItemSettings.Month);
yearTopItem = Options.Get(TopItemSettings.Year);
}
}
NamedOptions.cshtml:
@page
@model RazorPagesSample.Pages.NamedOptionsModel
@{
}
NamedOptions.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class NamedOptionsModel : PageModel
{
private readonly IOptionsSnapshot<TopItemSettings> _options;
public NamedOptionsModel(IOptionsSnapshot<TopItemSettings> options)
{
_options = options;
}
public ContentResult OnGet()
{
var monthTopItem = _options.Get(TopItemSettings.Month);
var yearTopItem = _options.Get(TopItemSettings.Year);
return Content(
$"Month:Name {monthTopItem.Name}\n" +
$"Month:Model {monthTopItem.Model}\n" +
$"Year:Name {yearTopItem.Name}\n" +
$"Year:Model {yearTopItem.Model}");
}
}
Все опции являются именованными экземплярами. Экземпляры IConfigureOptions<TOptions> считаются нацеленными на экземпляр Options.DefaultName, который имеет значение string.Empty. Интерфейс IConfigureNamedOptions<TOptions> также реализует интерфейс IConfigureOptions<TOptions>. Реализация IOptionsFactory<TOptions> по умолчанию содержит логику для правильного использования каждого компонента. Именованный параметр null предназначен для всех именованных экземпляров, а не для какого-то определенного.
ConfigureAll и PostConfigureAll следуют этому соглашению.
Руководство по последующей настройке именованных параметров представлено в разделе "Параметры после настройки ".
OptionsBuilder API-интерфейс
OptionsBuilder<TOptions> используется для настройки экземпляров TOptions.
OptionsBuilder упрощает создание именованных параметров, так как он является единственным параметром для первоначального вызова AddOptions<TOptions>(string optionsName) и не должен появляться во всех последующих вызовах. Проверка параметров и IConfigureOptions<TOptions> перегрузки, принимающие зависимости служб, доступны только через OptionsBuilder (см. «Использование служб DI для настройки параметров»).
OptionsBuilder<TOptions> показан в разделе проверки параметров .
Сведения о добавлении пользовательского репозитория см. в статье "Начало работы с API защиты данных" в ASP.NET Core.
Использование сервисов DI для настройки параметров
Использовать службы, доступные в результате внедрения зависимостей при настройке параметров, можно двумя способами.
Подход делегата конфигурации
Для внедрения зависимостей, когда службы регистрируются, передайте делегат конфигурации в Configure, который находится на OptionsBuilder<TOptions>.
OptionsBuilder<TOptions> предоставляет методы перегрузки Configure, позволяющие задействовать до пяти служб для настройки параметров.
builder.Services.AddOptions<PositionOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));
services.AddOptions<PositionOptions>("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>, имеющий конструктор, который принимает указанные типы служб.
Проверка параметров
Проверка параметров включает проверку значений параметров.
Рассмотрим следующую конфигурацию JSON:
"KeyOptions": {
"Key1": "Key One",
"Key2": 10,
"Key3": 32
}
Следующий класс используется для привязки к "KeyOptions" разделу конфигурации и применяет два правила заметок данных, которые включают регулярное выражение и требование диапазона.
KeyOptions.cs:
public class KeyOptions
{
public const string Key = "KeyOptions";
[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>, который привязывается к классу
KeyOptions; - Вызов ValidateDataAnnotations для включения проверки.
builder.Services.AddOptions<KeyOptions>()
.Bind(builder.Configuration.GetSection(KeyOptions.Key))
.ValidateDataAnnotations();
services.AddOptions<KeyOptions>()
.Bind(builder.Configuration.GetSection(KeyOptions.Key))
.ValidateDataAnnotations();
Метод ValidateDataAnnotations расширения определен в пакетеMicrosoft.Extensions.Options.DataAnnotations NuGet. Для веб-приложений, использующих пакет SDK Microsoft.NET.Sdk.Web, этот пакет используется неявно из общей среды.
Где службы регистрируются для внедрения зависимостей, в следующем примере применяется более сложное правило проверки с помощью делегата:
builder.Services.AddOptions<KeyOptions>()
.Bind(builder.Configuration.GetSection(KeyOptions.Key))
.ValidateDataAnnotations()
.Validate(options =>
{
return options.Key3 > options.Key2;
}, "Key3 must be > than Key2");
services.AddOptions<KeyOptions>()
.Bind(builder.Configuration.GetSection(KeyOptions.Key))
.ValidateDataAnnotations()
.Validate(options =>
{
return options.Key3 > options.Key2;
}, "Key3 must be > than Key2");
В следующем примере показано, как регистрировать исключения проверки параметров и отображать OptionsValidationException.Message.
Note
Для демонстрации в следующем примере используется MarkupString, чтобы форматировать "сырой" HTML-код. Отрисовка необработанного HTML-кода, созданного из любого ненадежного источника, является угрозой безопасности, и ее всегда следует избегать. Дополнительные сведения см. в статье Компоненты Razor ASP.NET Core.
OptionsValidation1.razor:
@page "/options-validation-1"
@inject IOptionsSnapshot<KeyOptions> Options
@inject ILogger<OptionsValidation1> Logger
@if (message is not null)
{
@((MarkupString)message)
}
@code {
private string? message;
protected override void OnInitialized()
{
try
{
var keyOptions = Options.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
Logger.LogError(failure);
}
}
try
{
message =
$"Key1: {Options.Value.Key1}<br>" +
$"Key2: {Options.Value.Key2}<br>" +
$"Key3: {Options.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
message = optValEx.Message;
}
}
}
В следующем примере показано, как регистрировать исключения, возникающие при проверке параметров, в конструкторе модели страницы и отображать OptionsValidationException.Message в методе OnGet этой страницы.
OptionsValidation1.cshtml:
@page
@model RazorPagesSample.Pages.OptionsValidation1Model
@{
}
OptionsValidation1.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class OptionsValidation1Model : PageModel
{
private readonly IOptionsSnapshot<KeyOptions>? _options;
public OptionsValidation1Model(IOptionsSnapshot<KeyOptions> options,
ILogger<OptionsValidation1Model> logger)
{
_options = options;
try
{
var keyOptions = _options?.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
logger?.LogError("Validation: {Failure}", failure);
}
}
}
public ContentResult OnGet()
{
string message;
try
{
message =
$"Key1: {_options?.Value.Key1}\n" +
$"Key2: {_options?.Value.Key2}\n" +
$"Key3: {_options?.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(message);
}
}
В приведенном выше коде отображаются значения конфигурации или ошибки проверки:
- Используйте код с предварительной конфигурацией параметров приложения, чтобы продемонстрировать отсутствие ошибок проверки.
- Измените конфигурацию в файле параметров приложения одним или несколькими способами, которые нарушают правила заметок данных. Перезагрузите страницу проверки параметров, чтобы увидеть нарушения правил.
В следующем примере содержимое страницы указывает, что значение Key2 не является диапазоном, если значение Key2 в файле параметров приложения изменяется на значение ниже нуля или более 1000:
DataAnnotation validation failed for 'KeyOptions' members: 'Key2' with the error: 'Value for Key2 must be between 0 and 1000.'.
Проверка параметров в выделенном классе с помощью IValidateOptions<TOptions>
Реализуйте IValidateOptions<TOptions> для проверки параметров без необходимости поддерживать правила проверки с использованием аннотаций данных или в файле приложения Program.
В следующем примере правила заметок данных и параметры делегирования проверки предыдущих примеров перемещаются в класс проверки. Класс модели Options (KeyOptions2) не содержит аннотаций данных.
Рассмотрим следующую конфигурацию JSON:
"KeyOptions": {
"Key1": "Key One",
"Key2": 10,
"Key3": 32
}
KeyOptions2.cs:
public class KeyOptions2
{
public const string Key = "KeyOptions";
public string? Key1 { get; set; }
public int Key2 { get; set; }
public int Key3 { get; set; }
}
public class KeyOptionsValidation : IValidateOptions<KeyOptions2>
{
public ValidateOptionsResult Validate(string? name, KeyOptions2 options)
{
if (options == null)
{
return ValidateOptionsResult.Fail("KeyOptions not found.");
}
StringBuilder? validationResult = new();
var rx = new Regex(@"^[a-zA-Z\s]{1,40}$");
var match = rx.Match(options.Key1!);
if (string.IsNullOrEmpty(match.Value))
{
validationResult.Append($"{options.Key1} doesn't match RegEx<br>");
}
if (options.Key2 < 0 || options.Key2 > 1000)
{
validationResult.Append($"{options.Key2} doesn't match Range 0 - 1000<br>");
}
if (options.Key3 < options.Key2)
{
validationResult.Append("Key3 must be > than Key2<br>");
}
if (validationResult.Length > 0)
{
return ValidateOptionsResult.Fail(validationResult.ToString());
}
return ValidateOptionsResult.Success;
}
}
public class KeyOptionsValidation : IValidateOptions<KeyOptions2>
{
public ValidateOptionsResult Validate(string? name, KeyOptions2 options)
{
if (options == null)
{
return ValidateOptionsResult.Fail("KeyOptions not found");
}
StringBuilder? validationResult = new();
var rx = new Regex(@"^[a-zA-Z\s]{1,40}$");
var match = rx.Match(options.Key1!);
if (string.IsNullOrEmpty(match.Value))
{
validationResult.Append($"{options.Key1} doesn't match RegEx\n");
}
if (options.Key2 < 0 || options.Key2 > 1000)
{
validationResult.Append($"{options.Key2} doesn't match Range 0 - 1000\n");
}
if (options.Key3 < options.Key2)
{
validationResult.Append("Key3 must be > than Key2\n");
}
if (validationResult.Length > 0)
{
return ValidateOptionsResult.Fail(validationResult.ToString());
}
return ValidateOptionsResult.Success;
}
}
Где службы регистрируются для внедрения зависимостей и используют предыдущий код, проверка включена в Program.cs следующем примере:
builder.Services.Configure<KeyOptions2>(
builder.Configuration.GetSection(KeyOptions2.Key));
builder.Services.AddSingleton<IValidateOptions<KeyOptions2>,
KeyOptionsValidation>();
services.Configure<KeyOptions>(
builder.Configuration.GetSection(KeyOptions2.Key));
services.AddSingleton<IValidateOptions<KeyOptions2>, KeyOptionsValidation>();
В следующем примере показано, как регистрировать исключения проверки параметров и отображать OptionsValidationException.Message.
OptionsValidation2.razor:
@page "/options-validation-2"
@inject IOptionsSnapshot<KeyOptions2> Options
@inject ILogger<OptionsValidation2> Logger
@if (message is not null)
{
@((MarkupString)message)
}
@code {
private string? message;
protected override void OnInitialized()
{
try
{
var keyOptions = Options.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
Logger.LogError(failure);
}
}
try
{
message =
$"Key1: {Options.Value.Key1}<br>" +
$"Key2: {Options.Value.Key2}<br>" +
$"Key3: {Options.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
message = optValEx.Message;
}
}
}
В следующем примере показано, как регистрировать исключения, возникающие при проверке параметров, в конструкторе модели страницы и отображать OptionsValidationException.Message в методе OnGet этой страницы.
OptionsValidation2.cshtml:
@page
@model RazorPagesSample.Pages.OptionsValidation2Model
@{
}
OptionsValidation2.cshtml.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
namespace RazorPagesSample.Pages;
public class OptionsValidation2Model : PageModel
{
private readonly IOptionsSnapshot<KeyOptions2>? _options;
public OptionsValidation2Model(IOptionsSnapshot<KeyOptions2> options,
ILogger<OptionsValidation2Model> logger)
{
_options = options;
try
{
var keyOptions = _options?.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
logger?.LogError("Validation: {Failure}", failure);
}
}
}
public ContentResult OnGet()
{
string message;
try
{
message =
$"Key1: {_options?.Value.Key1}\n" +
$"Key2: {_options?.Value.Key2}\n" +
$"Key3: {_options?.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(message);
}
}
Проверка уровня класса с помощью IValidatableObject
Проверка параметров поддерживает IValidatableObject выполнение валидации на уровне класса для класса внутри класса.
- Реализуйте интерфейс IValidatableObject и его метод Validate внутри класса.
- Вызовите ValidateDataAnnotations в файле
Program.
Выполнение проверки параметров при запуске приложения ValidateOnStart
Проверка параметров выполняется при первом создании экземпляра TOption, то есть при первом доступе к конвейеру запроса IOptionsSnapshot<TOptions>.Value или при вызове IOptionsMonitor<TOptions>.Get(string). При каждом перезагрузе параметров проверка выполняется снова.
Чтобы выполнить проверку параметров при запуске приложения, вызовите ValidateOnStart, в Program файле, где службы регистрируются для внедрения зависимостей:
builder.Services.AddOptions<KeyOptions>()
.Bind(builder.Configuration.GetSection(KeyOptions.Key))
.ValidateDataAnnotations()
.ValidateOnStart();
Параметры после настройки
При регистрации служб для внедрения зависимостей, PostConfigure доступен для инициализации определенного именованного параметра. В следующем примере только TopItem:Month:Name сконфигурировано дополнительно.
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>(TopItemSettings.Month, options =>
{
options.Name = "Blue Gizmo";
});
services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
services.PostConfigure<TopItemSettings>(TopItemSettings.Month, options =>
{
options.Name = "Blue Gizmo";
});
Отображенный результат из примера именованных параметров, где после настройки изменён только TopItem:Month:Name.
Month: Name: Blue Gizmo Model: GW46
Year: Name: Orange Gadget Model: OG35
Для регистрации служб в системе внедрения зависимостей используйте PostConfigureAll для инициализации всех именованных экземпляров указанного типа параметров. В следующем примере для всех экземпляров TopItem.Name задано значение Blue Gizmo:
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigureAll<TopItemSettings>(options =>
{
options.Name = "Blue Gizmo";
});
services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
services.PostConfigureAll<TopItemSettings>(options =>
{
options.Name = "Blue Gizmo";
});
Отрисованные выходные данные из примера именованных параметров, где все TopItem:Name параметры настроены после настройки:
Month: Name: Blue Gizmo Model: GW46
Year: Name: Blue Gizmo Model: OG35
Параметры доступа в конвейере обработки запросов
Чтобы получить доступ к IOptions<TOptions> или IOptionsMonitor<TOptions> в конвейере обработки запросов, вызовите GetRequiredService на WebApplication.Services:
var name = app.Services.GetRequiredService<IOptionsMonitor<PositionOptions>>()
.CurrentValue.Name;
IOptions<TOptions> и IOptionsMonitor<TOptions> можно использовать в методе Startup.Configure, так как службы создаются до выполнения метода Configure.
В следующем примере IOptionsMonitor<TOptions> внедряется в метод Startup.Configure, чтобы получить значения PositionOptions.
public void Configure(IApplicationBuilder app,
IOptionsMonitor<PositionOptions> options)
Доступ к параметрам в конвейере Startup.Configureобработки запросов:
var name = options.CurrentValue.Name;
Не используйте IOptions<TOptions> или IOptionsMonitor<TOptions> в Startup.ConfigureServices. Из-за очередности регистрации служб состояние параметров может быть несогласованным.
Дополнительные ресурсы
ASP.NET Core