Padrão de opções no ASP.NET Core
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Por Rick Anderson.
O padrão de opções usa classes para fornecer acesso fortemente tipado a grupos de configurações relacionadas. Quando as definições de configuração são isoladas por cenário em classes separadas, o aplicativo segue dois princípios importantes de engenharia de software:
- Encapsulamento:
- Classes que dependem de definições de configuração dependem apenas das definições de configuração usadas por elas.
- Separação de Interesses:
- As configurações para diferentes partes do aplicativo não são dependentes nem acopladas entre si.
As opções também fornecem um mecanismo para validar os dados da configuração. Para obter mais configurações, consulte a seção Validação de opções.
Esse artigo fornece informações sobre o padrão de opções no ASP.NET Core. Para obter informações sobre como usar o padrão de opções em aplicativos de console, consulte Padrão de opções no .NET.
Associar configuração hierárquica
A maneira preferencial de ler os valores de configuração relacionados é usando o padrão de opções. Por exemplo, para ler os seguintes valores de configuração:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crie a seguinte classe PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Uma classe de opções:
- Deve ser não abstrato.
- Tem propriedades públicas de leitura/gravação do tipo que têm itens correspondentes na configuração que são associadas.
- Tem propriedades de leitura/gravação associadas às entradas correspondentes na configuração.
- Não tem os campos associados. No código anterior,
Position
não está vinculado. O campoPosition
é usado para que a cadeia de caracteres"Position"
não precise ser codificada no aplicativo ao associar a classe a um provedor de configuração.
O seguinte código:
- Chama ConfigurationBinder.Bind para associar a classe
PositionOptions
à seçãoPosition
. - Exibe os dados de configuração de
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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
ConfigurationBinder.Get<T>
associa e retorna o tipo especificado. ConfigurationBinder.Get<T>
pode ser mais conveniente do que usar ConfigurationBinder.Bind
. O código a seguir mostra como usar ConfigurationBinder.Get<T>
com a classe 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
A associação também permite a concretização de uma classe abstrata. Considere o código a seguir que usa a classe abstrata 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;
}
O código a seguir exibe os valores de configuração 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}"
);
}
}
As chamadas para Bind
são menos restritas do que as chamadas para Get<>
:
Bind
permite a concretização de um resumo.Get<>
tem que criar uma instância própria.
O padrão de opções
Uma abordagem alternativa ao usar o padrão de opções é associar a seção Position
e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions
é adicionada ao contêiner de serviço com Configure e associada à configuração:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Usando o código anterior, o código a seguir lê as opções de posição:
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}");
}
}
No código anterior, as alterações no arquivo de configuração JSON após a inicialização do aplicativo não são lidas. Para ler as alterações após a inicialização do aplicativo, use IOptionsSnapshot.
Interfaces de opções
- Não é compatível com:
- Leitura de dados de configuração após o início do aplicativo.
- Opções nomeadas
- É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- É útil em cenários em que as opções devem ser novamente computadas em cada solicitação. Para obter mais informações, consulte Usar IOptionsSnapshot para ler dados atualizados.
- É registrado como Escopo e, portanto, não pode ser injetado em um serviço de banco de dados individual.
- Permite opções nomeadas
- O é usado para recuperar as opções e gerenciar notificações de opções para instâncias de
TOptions
. - É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- Suporte:
- Notificações de alteração
- Opções nomeadas
- Configuração recarregável
- Invalidação seletiva de opções (IOptionsMonitorCache<TOptions>)
Os cenários de Pós-configuração permitem definir ou alterar as opções depois de todas as configurações do IConfigureOptions<TOptions> serem feitas.
O IOptionsFactory<TOptions> é responsável por criar novas instâncias de opções. Ele tem um único método Create. A implementação padrão usa todos os IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrados e executa todas as configurações primeiro, seguidas da pós-configuração. Ela faz distinção entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chama apenas a interface apropriada.
O IOptionsMonitorCache<TOptions> é usado pelo IOptionsMonitor<TOptions> para armazenar em cache as instâncias do TOptions
. O IOptionsMonitorCache<TOptions> invalida as instâncias de opções no monitor, de modo que o valor seja recalculado (TryRemove). Os valores podem ser manualmente inseridos com TryAdd. O método Clear é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.
Usar IOptionsSnapshot para ler dados atualizados
Usando IOptionsSnapshot<TOptions>:
- As opções são calculadas uma vez por solicitação, quando acessadas e armazenadas em cache durante o tempo de vida da solicitação.
- Pode incorrer em uma penalidade significativa de desempenho porque é um serviço com escopo e é computado novamente por solicitação. Para obter mais informações, consulte este problema do GitHub e Aprimorar o desempenho da associação de configuração.
- As alterações na configuração são lidas depois que o aplicativo é iniciado ao usar provedores de configuração que permitem a leitura de valores de configuração atualizados.
A diferença entre IOptionsMonitor
e IOptionsSnapshot
é que:
IOptionsMonitor
é um serviço de banco de dados individual que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências de banco de dados individual.IOptionsSnapshot
é um serviço com escopo e fornece um instantâneo das opções no momento em que o objetoIOptionsSnapshot<T>
é construído. Os instantâneos de opções são projetados para uso com dependências transitórias e com escopo.
O código a seguir usa 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}");
}
}
O código a seguir registra uma instância de configuração que MyOptions
associa a:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
No código anterior, as alterações no arquivo de configuração JSON após o aplicativo iniciar a leitura.
IOptionsMonitor
O código a seguir registra uma instância de configuração a que MyOptions
se associa.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
O exemplo a seguir usa 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
Compatibilidade de opções nomeadas usando IConfigureNamedOptions
Opções nomeadas:
- São úteis quando várias seções de configuração se associam às mesmas propriedades.
- Diferencia maiúsculas de minúsculas.
Considere o seguinte arquivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Em vez de criar duas classes para associar TopItem:Month
e TopItem:Year
, a seguinte classe é usada para cada seção:
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;
}
O código a seguir configura as opções nomeadas:
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();
O código a seguir mostra as opções nomeadas:
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" );
}
}
Todas as opções são instâncias nomeadas. As instâncias IConfigureOptions<TOptions> existentes são tratadas como sendo direcionadas à instância Options.DefaultName
, que é string.Empty
. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão de IOptionsFactory<TOptions> tem lógica para usar cada um de forma adequada. A opção nomeada null
é usada para direcionar todas as instâncias nomeadas, em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll usam essa convenção.
API OptionsBuilder
OptionsBuilder<TOptions> é usada para configurar instâncias TOptions
. OptionsBuilder
simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada AddOptions<TOptions>(string optionsName)
inicial, em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as sobrecargas ConfigureOptions
que aceitam dependências de serviço só estão disponíveis por meio de OptionsBuilder
.
OptionsBuilder
é usado na seção Validação de opções.
Consulte Usar AddOptions para configurar o repositório personalizado para obter informações sobre como adicionar um repositório personalizado.
Usar os serviços de injeção de dependência para configurar as opções
Os serviços podem ser acessados de injeção de dependência ao configurar as opções de duas maneiras:
Aprova um representante de configuração para Configure em OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
oferece sobrecargas de Configure que permitem usar até cinco serviços para configurar opções: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));
Criar um tipo que implementa IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registra o tipo como um serviço.
É recomendável transmitir um delegado de configuração para Configure, já que a criação de um serviço é algo mais complexo. A criação de um tipo é equivalente ao que a estrutura faz ao chamar Configure. Chamar Configure registra um genérico transitório IConfigureNamedOptions<TOptions>, que tem um construtor que aceita os tipos de serviço genérico especificados.
Validação de opções
A validação de opções permite que valores de opção sejam validados.
Considere o seguinte arquivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
A classe a seguir é usada para se associar à seção de configuração "MyConfig"
e aplica algumas regras de 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; }
}
O seguinte código:
- Chama AddOptions para obter um OptionsBuilder<TOptions> que se associa à classe
MyConfigOptions
. - Chamadas ValidateDataAnnotations para habilitar a validação usando
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();
O método de extensão ValidateDataAnnotations
é definido no pacote Microsoft.Extensions.Options.DataAnnotations do NuGet. Para aplicativos Web que usam o SDK Microsoft.NET.Sdk.Web
, esse pacote é referenciado implicitamente da estrutura compartilhada.
O código a seguir exibe os valores de configuração ou os erros de validação:
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);
}
O código a seguir aplica uma regra de validação mais complexa usando um representante:
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>
e IValidatableObject
A classe a seguir implementa 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
permite retirar o código de validação do Program.cs
e colocar em uma classe.
Usando o código anterior, a validação está habilitada em Program.cs
com o seguinte código:
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();
A validação de opções também dá suporte a IValidatableObject. Para executar a validação de nível de classe de uma classe dentro da própria classe:
- Implemente a interface
IValidatableObject
e seu método Validate dentro da classe . - Chame ValidateDataAnnotations em
Program.cs
.
ValidateOnStart
A validação de opções é executada na primeira vez que uma implementação IOptions<TOptions>, IOptionsSnapshot<TOptions>ou IOptionsMonitor<TOptions> é criada. Para executar rapidamente a validação de opções, quando o aplicativo for iniciado, chame ValidateOnStart em Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Pós-configuração de opções
Defina a pós-configuração com IPostConfigureOptions<TOptions>. A pós-configuração é executada depois que toda o configuração de IConfigureOptions<TOptions> é feita:
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";
});
O PostConfigure está disponível para pós-configurar opções nomeadas:
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();
Use PostConfigureAll para pós-configurar todas as instâncias de configuração:
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";
});
Opções de acesso no Program.cs
Para acessar IOptions<TOptions> ou IOptionsMonitor<TOptions> em Program.cs
, chame GetRequiredService em WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Recursos adicionais
Por Kirk Larkin e Rick Anderson.
O padrão de opções usa classes para fornecer acesso fortemente tipado a grupos de configurações relacionadas. Quando as definições de configuração são isoladas por cenário em classes separadas, o aplicativo segue dois princípios importantes de engenharia de software:
- Encapsulamento:
- Classes que dependem de definições de configuração dependem apenas das definições de configuração usadas por elas.
- Separação de Interesses:
- As configurações para diferentes partes do aplicativo não são dependentes nem acopladas entre si.
As opções também fornecem um mecanismo para validar os dados da configuração. Para obter mais configurações, consulte a seção Validação de opções.
Esse artigo fornece informações sobre o padrão de opções no ASP.NET Core. Para obter informações sobre como usar o padrão de opções em aplicativos de console, consulte Padrão de opções no .NET.
Associar configuração hierárquica
A maneira preferencial de ler os valores de configuração relacionados é usando o padrão de opções. Por exemplo, para ler os seguintes valores de configuração:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crie a seguinte classe PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Uma classe de opções:
- Deve ser não abstrata e com um construtor público sem parâmetros.
- Todas as propriedades públicas de leitura e gravação do tipo são vinculadas.
- Os campos não são vinculados. No código anterior,
Position
não está vinculado. O campoPosition
é usado para que a cadeia de caracteres"Position"
não precise ser codificada no aplicativo ao associar a classe a um provedor de configuração.
O seguinte código:
- Chama ConfigurationBinder.Bind para associar a classe
PositionOptions
à seçãoPosition
. - Exibe os dados de configuração de
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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
ConfigurationBinder.Get<T>
associa e retorna o tipo especificado. ConfigurationBinder.Get<T>
pode ser mais conveniente do que usar ConfigurationBinder.Bind
. O código a seguir mostra como usar ConfigurationBinder.Get<T>
com a classe 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
Uma abordagem alternativa ao usar o padrão de opções é associar a seção Position
e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions
é adicionada ao contêiner de serviço com Configure e associada à configuração:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Usando o código anterior, o código a seguir lê as opções de posição:
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}");
}
}
No código anterior, as alterações no arquivo de configuração JSON após a inicialização do aplicativo não são lidas. Para ler as alterações após a inicialização do aplicativo, use IOptionsSnapshot.
Interfaces de opções
- Não é compatível com:
- Leitura de dados de configuração após o início do aplicativo.
- Opções nomeadas
- É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- É útil em cenários em que as opções devem ser novamente computadas em cada solicitação. Para obter mais informações, consulte Usar IOptionsSnapshot para ler dados atualizados.
- É registrado como Escopo e, portanto, não pode ser injetado em um serviço de banco de dados individual.
- Permite opções nomeadas
- O é usado para recuperar as opções e gerenciar notificações de opções para instâncias de
TOptions
. - É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- Suporte:
- Notificações de alteração
- Opções nomeadas
- Configuração recarregável
- Invalidação seletiva de opções (IOptionsMonitorCache<TOptions>)
Os cenários de Pós-configuração permitem definir ou alterar as opções depois de todas as configurações do IConfigureOptions<TOptions> serem feitas.
O IOptionsFactory<TOptions> é responsável por criar novas instâncias de opções. Ele tem um único método Create. A implementação padrão usa todos os IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrados e executa todas as configurações primeiro, seguidas da pós-configuração. Ela faz distinção entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chama apenas a interface apropriada.
O IOptionsMonitorCache<TOptions> é usado pelo IOptionsMonitor<TOptions> para armazenar em cache as instâncias do TOptions
. O IOptionsMonitorCache<TOptions> invalida as instâncias de opções no monitor, de modo que o valor seja recalculado (TryRemove). Os valores podem ser manualmente inseridos com TryAdd. O método Clear é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.
Usar IOptionsSnapshot para ler dados atualizados
Usando IOptionsSnapshot<TOptions>:
- As opções são calculadas uma vez por solicitação, quando acessadas e armazenadas em cache durante o tempo de vida da solicitação.
- Pode incorrer em uma penalidade significativa de desempenho porque é um serviço com escopo e é computado novamente por solicitação. Para obter mais informações, consulte este problema do GitHub e Aprimorar o desempenho da associação de configuração.
- As alterações na configuração são lidas depois que o aplicativo é iniciado ao usar provedores de configuração que permitem a leitura de valores de configuração atualizados.
A diferença entre IOptionsMonitor
e IOptionsSnapshot
é que:
IOptionsMonitor
é um serviço de banco de dados individual que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências de banco de dados individual.IOptionsSnapshot
é um serviço com escopo e fornece um instantâneo das opções no momento em que o objetoIOptionsSnapshot<T>
é construído. Os instantâneos de opções são projetados para uso com dependências transitórias e com escopo.
O código a seguir usa 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}");
}
}
O código a seguir registra uma instância de configuração que MyOptions
associa a:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
No código anterior, as alterações no arquivo de configuração JSON após o aplicativo iniciar a leitura.
IOptionsMonitor
O código a seguir registra uma instância de configuração a que MyOptions
se associa.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
O exemplo a seguir usa 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
Compatibilidade de opções nomeadas usando IConfigureNamedOptions
Opções nomeadas:
- São úteis quando várias seções de configuração se associam às mesmas propriedades.
- Diferencia maiúsculas de minúsculas.
Considere o seguinte arquivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Em vez de criar duas classes para associar TopItem:Month
e TopItem:Year
, a seguinte classe é usada para cada seção:
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;
}
O código a seguir configura as opções nomeadas:
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();
O código a seguir mostra as opções nomeadas:
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" );
}
}
Todas as opções são instâncias nomeadas. As instâncias IConfigureOptions<TOptions> existentes são tratadas como sendo direcionadas à instância Options.DefaultName
, que é string.Empty
. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão de IOptionsFactory<TOptions> tem lógica para usar cada um de forma adequada. A opção nomeada null
é usada para direcionar todas as instâncias nomeadas, em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll usam essa convenção.
API OptionsBuilder
OptionsBuilder<TOptions> é usada para configurar instâncias TOptions
. OptionsBuilder
simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada AddOptions<TOptions>(string optionsName)
inicial, em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as sobrecargas ConfigureOptions
que aceitam dependências de serviço só estão disponíveis por meio de OptionsBuilder
.
OptionsBuilder
é usado na seção Validação de opções.
Consulte Usar AddOptions para configurar o repositório personalizado para obter informações sobre como adicionar um repositório personalizado.
Usar os serviços de injeção de dependência para configurar as opções
Os serviços podem ser acessados de injeção de dependência ao configurar as opções de duas maneiras:
Aprova um representante de configuração para Configure em OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
oferece sobrecargas de Configure que permitem usar até cinco serviços para configurar opções: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));
Criar um tipo que implementa IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registra o tipo como um serviço.
É recomendável transmitir um delegado de configuração para Configure, já que a criação de um serviço é algo mais complexo. A criação de um tipo é equivalente ao que a estrutura faz ao chamar Configure. Chamar Configure registra um genérico transitório IConfigureNamedOptions<TOptions>, que tem um construtor que aceita os tipos de serviço genérico especificados.
Validação de opções
A validação de opções permite que valores de opção sejam validados.
Considere o seguinte arquivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
A classe a seguir é usada para se associar à seção de configuração "MyConfig"
e aplica algumas regras de 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; }
}
O seguinte código:
- Chama AddOptions para obter um OptionsBuilder<TOptions> que se associa à classe
MyConfigOptions
. - Chamadas ValidateDataAnnotations para habilitar a validação usando
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();
O método de extensão ValidateDataAnnotations
é definido no pacote Microsoft.Extensions.Options.DataAnnotations do NuGet. Para aplicativos Web que usam o SDK Microsoft.NET.Sdk.Web
, esse pacote é referenciado implicitamente da estrutura compartilhada.
O código a seguir exibe os valores de configuração ou os erros de validação:
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);
}
O código a seguir aplica uma regra de validação mais complexa usando um representante:
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>
e IValidatableObject
A classe a seguir implementa 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
permite retirar o código de validação do Program.cs
e colocar em uma classe.
Usando o código anterior, a validação está habilitada em Program.cs
com o seguinte código:
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();
A validação de opções também dá suporte a IValidatableObject. Para executar a validação de nível de classe de uma classe dentro da própria classe:
- Implemente a interface
IValidatableObject
e seu método Validate dentro da classe . - Chame ValidateDataAnnotations em
Program.cs
.
ValidateOnStart
A validação de opções é executada na primeira vez que uma implementação IOptions<TOptions>, IOptionsSnapshot<TOptions>ou IOptionsMonitor<TOptions> é criada. Para executar rapidamente a validação de opções, quando o aplicativo for iniciado, chame ValidateOnStart em Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Pós-configuração de opções
Defina a pós-configuração com IPostConfigureOptions<TOptions>. A pós-configuração é executada depois que toda o configuração de IConfigureOptions<TOptions> é feita:
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";
});
O PostConfigure está disponível para pós-configurar opções nomeadas:
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();
Use PostConfigureAll para pós-configurar todas as instâncias de configuração:
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";
});
Opções de acesso no Program.cs
Para acessar IOptions<TOptions> ou IOptionsMonitor<TOptions> em Program.cs
, chame GetRequiredService em WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Recursos adicionais
Por Kirk Larkin e Rick Anderson.
O padrão de opções usa classes para fornecer acesso fortemente tipado a grupos de configurações relacionadas. Quando as definições de configuração são isoladas por cenário em classes separadas, o aplicativo segue dois princípios importantes de engenharia de software:
- Encapsulamento:
- Classes que dependem de definições de configuração dependem apenas das definições de configuração usadas por elas.
- Separação de Interesses:
- As configurações para diferentes partes do aplicativo não são dependentes nem acopladas entre si.
As opções também fornecem um mecanismo para validar os dados da configuração. Para obter mais configurações, consulte a seção Validação de opções.
Esse tópico fornece informações sobre o padrão de opções no ASP.NET Core. Para obter informações sobre como usar o padrão de opções em aplicativos de console, consulte Padrão de opções no .NET.
Exibir ou baixar código de exemplo (como baixar)
Associar configuração hierárquica
A maneira preferencial de ler os valores de configuração relacionados é usando o padrão de opções. Por exemplo, para ler os seguintes valores de configuração:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crie a seguinte classe PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
Uma classe de opções:
- Deve ser não abstrata e com um construtor público sem parâmetros.
- Todas as propriedades públicas de leitura e gravação do tipo são vinculadas.
- Os campos não são vinculados. No código anterior,
Position
não está vinculado. A propriedadePosition
é usada para que a cadeia de caracteres"Position"
não precise ser codificada no aplicativo ao associar a classe a um provedor de configuração.
O seguinte código:
- Chama ConfigurationBinder.Bind para associar a classe
PositionOptions
à seçãoPosition
. - Exibe os dados de configuração de
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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
ConfigurationBinder.Get<T>
associa e retorna o tipo especificado. ConfigurationBinder.Get<T>
pode ser mais conveniente do que usar ConfigurationBinder.Bind
. O código a seguir mostra como usar ConfigurationBinder.Get<T>
com a classe 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
Uma abordagem alternativa ao usar o padrão de opções é associar a seção Position
e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions
é adicionada ao contêiner de serviço com Configure e associada à configuração:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
Usando o código anterior, o código a seguir lê as opções de posição:
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}");
}
}
No código anterior, as alterações no arquivo de configuração JSON após a inicialização do aplicativo não são lidas. Para ler as alterações após a inicialização do aplicativo, use IOptionsSnapshot.
Interfaces de opções
- Não é compatível com:
- Leitura de dados de configuração após o início do aplicativo.
- Opções nomeadas
- É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- É útil em cenários em que as opções devem ser novamente computadas em cada solicitação. Para obter mais informações, consulte Usar IOptionsSnapshot para ler dados atualizados.
- É registrado como Escopo e, portanto, não pode ser injetado em um serviço Singleton.
- Permite opções nomeadas
- O é usado para recuperar as opções e gerenciar notificações de opções para instâncias de
TOptions
. - É registrado como singleton e pode ser injetado em qualquer tempo de vida do serviço.
- Suporte:
- Notificações de alteração
- Opções nomeadas
- Configuração recarregável
- Invalidação seletiva de opções (IOptionsMonitorCache<TOptions>)
Os cenários de Pós-configuração permitem definir ou alterar as opções depois de todas as configurações do IConfigureOptions<TOptions> serem feitas.
O IOptionsFactory<TOptions> é responsável por criar novas instâncias de opções. Ele tem um único método Create. A implementação padrão usa todos os IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrados e executa todas as configurações primeiro, seguidas da pós-configuração. Ela faz distinção entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chama apenas a interface apropriada.
O IOptionsMonitorCache<TOptions> é usado pelo IOptionsMonitor<TOptions> para armazenar em cache as instâncias do TOptions
. O IOptionsMonitorCache<TOptions> invalida as instâncias de opções no monitor, de modo que o valor seja recalculado (TryRemove). Os valores podem ser manualmente inseridos com TryAdd. O método Clear é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.
Usar IOptionsSnapshot para ler dados atualizados
Usando IOptionsSnapshot<TOptions>, as opções são computadas uma vez por solicitação, quando acessadas e armazenadas em cache durante o tempo de vida da solicitação. As alterações na configuração são lidas depois que o aplicativo é iniciado ao usar provedores de configuração que permitem a leitura de valores de configuração atualizados.
A diferença entre IOptionsMonitor
e IOptionsSnapshot
é que:
IOptionsMonitor
é um serviço de banco de dados individual que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências de banco de dados individual.IOptionsSnapshot
é um serviço com escopo e fornece um instantâneo das opções no momento em que o objetoIOptionsSnapshot<T>
é construído. Os instantâneos de opções são projetados para uso com dependências transitórias e com escopo.
O código a seguir usa 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}");
}
}
O código a seguir registra uma instância de configuração que MyOptions
associa a:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
No código anterior, as alterações no arquivo de configuração JSON após o aplicativo iniciar a leitura.
IOptionsMonitor
O código a seguir registra uma instância de configuração a que MyOptions
se associa.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
O exemplo a seguir usa 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}");
}
}
No código anterior, por padrão, as alterações no arquivo de configuração JSON após a inicialização do aplicativo são lidas.
Compatibilidade de opções nomeadas usando IConfigureNamedOptions
Opções nomeadas:
- São úteis quando várias seções de configuração se associam às mesmas propriedades.
- Diferencia maiúsculas de minúsculas.
Considere o seguinte arquivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Em vez de criar duas classes para associar TopItem:Month
e TopItem:Year
, a seguinte classe é usada para cada seção:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
O código a seguir configura as opções nomeadas:
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();
}
O código a seguir mostra as opções nomeadas:
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" );
}
}
Todas as opções são instâncias nomeadas. As instâncias IConfigureOptions<TOptions> existentes são tratadas como sendo direcionadas à instância Options.DefaultName
, que é string.Empty
. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão de IOptionsFactory<TOptions> tem lógica para usar cada um de forma adequada. A opção nomeada null
é usada para direcionar todas as instâncias nomeadas, em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll usam essa convenção.
API OptionsBuilder
OptionsBuilder<TOptions> é usada para configurar instâncias TOptions
. OptionsBuilder
simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada AddOptions<TOptions>(string optionsName)
inicial, em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as sobrecargas ConfigureOptions
que aceitam dependências de serviço só estão disponíveis por meio de OptionsBuilder
.
OptionsBuilder
é usado na seção Validação de opções.
Consulte Usar AddOptions para configurar o repositório personalizado para obter informações sobre como adicionar um repositório personalizado.
Usar os serviços de injeção de dependência para configurar as opções
Os serviços podem ser acessados de injeção de dependência ao configurar as opções de duas maneiras:
Aprova um representante de configuração para Configure em OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
oferece sobrecargas de Configure que permitem usar até cinco serviços para configurar opções:services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Criar um tipo que implementa IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registra o tipo como um serviço.
É recomendável transmitir um delegado de configuração para Configure, já que a criação de um serviço é algo mais complexo. A criação de um tipo é equivalente ao que a estrutura faz ao chamar Configure. Chamar Configure registra um genérico transitório IConfigureNamedOptions<TOptions>, que tem um construtor que aceita os tipos de serviço genérico especificados.
Validação de opções
A validação de opções permite que valores de opção sejam validados.
Considere o seguinte arquivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
A classe a seguir associa-se à seção de configuração "MyConfig"
e aplica algumas regras de 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; }
}
O seguinte código:
- Chama AddOptions para obter um OptionsBuilder<TOptions> que se associa à classe
MyConfigOptions
. - Chamadas ValidateDataAnnotations para habilitar a validação usando
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();
}
O método de extensão ValidateDataAnnotations
é definido no pacote Microsoft.Extensions.Options.DataAnnotations do NuGet. Para aplicativos Web que usam o SDK Microsoft.NET.Sdk.Web
, esse pacote é referenciado implicitamente da estrutura compartilhada.
O código a seguir exibe os valores de configuração ou os erros de validação:
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);
}
O código a seguir aplica uma regra de validação mais complexa usando um representante:
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 para validação complexa
A classe a seguir implementa 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
permite retirar o código de validação do StartUp
e colocar em uma classe.
Usando o código anterior, a validação está habilitada em Startup.ConfigureServices
com o seguinte código:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
Pós-configuração de opções
Defina a pós-configuração com IPostConfigureOptions<TOptions>. A pós-configuração é executada depois que toda o configuração de IConfigureOptions<TOptions> é feita:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
O PostConfigure está disponível para pós-configurar opções nomeadas:
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Use PostConfigureAll para pós-configurar todas as instâncias de configuração:
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Acessando opções durante a inicialização
IOptions<TOptions> e IOptionsMonitor<TOptions> podem ser usados em Startup.Configure
, pois os serviços são criados antes da execução do método Configure
.
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
Não use IOptions<TOptions> ou IOptionsMonitor<TOptions> em Startup.ConfigureServices
. Pode haver um estado inconsistente de opções devido à ordenação dos registros de serviço.
Pacote NuGet Options.ConfigurationExtensions
O pacote Microsoft.Extensions.Options.ConfigurationExtensions é referenciado implicitamente em aplicativos ASP.NET Core.