Partilhar via


Padrão de opções no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 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 adere a dois princípios importantes de engenharia de software:

  • Encapsulamento:
    • As classes que dependem das definições de configuração dependem apenas das definições de configuração que utilizam.
  • Separação de preocupações:
    • As configurações de diferentes partes do aplicativo não são dependentes ou acopladas umas às outras.

As opções também fornecem um mecanismo para validar dados de configuração. Para obter mais informações, consulte a seção Validação de opções.

Este 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.

Vincular configuração hierárquica

A maneira preferida 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 PositionOptions classe:

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 estão vinculados.
  • Tem suas propriedades de leitura-gravação vinculadas a entradas correspondentes na configuração.
  • Não tem seus campos vinculados. No código anterior, Position não está vinculado. O Position campo é usado para que a cadeia de caracteres "Position" não precise ser codificada no aplicativo ao vincular a classe a um provedor de configuração.

O seguinte código:

  • Chama ConfigurationBinder.Bind para vincular a PositionOptions classe à Position seção.
  • Exibe os dados de Position configuração.
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 o início do aplicativo são lidas.

ConfigurationBinder.Get<T> Vincula 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 PositionOptions classe:

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 o início do aplicativo são lidas.

Bind também permite a concretização de uma classe abstrata. Considere o seguinte código que usa a classe SomethingWithANameabstrata :

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 NameTitleOptions configuração:

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 são menos rigorosas do que as Bind 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 é vincular a Position seção e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions é adicionado ao contêiner de serviço com Configure e vinculado à 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 ficheiro de configuração JSON após o início da aplicação não são lidas. Para ler as alterações após o início do aplicativo, use IOptionsSnapshot.

Opções de interfaces

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Os cenários pós-configuração permitem definir ou alterar opções depois que toda a IConfigureOptions<TOptions> configuração ocorre.

IOptionsFactory<TOptions> é responsável pela criação de novas instâncias de opções. Tem um único Create método. A implementação padrão leva todos os registrados IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> executa todas as configurações primeiro, seguido pela pós-configuração. Ele distingue entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> só chama a interface apropriada.

IOptionsMonitorCache<TOptions> é usado por para armazenar instâncias em IOptionsMonitor<TOptions> cache TOptions . O IOptionsMonitorCache<TOptions> invalida instâncias de opções no monitor para que o valor seja recalculado (TryRemove). Os valores podem ser introduzidos manualmente com TryAdd. O Clear método é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.

Use IOptionsSnapshot para ler dados atualizados

Utilização de 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 de desempenho significativa porque é um serviço com escopo e é recalculado por solicitação. Para obter mais informações, consulte este problema do GitHub e Melhorar o desempenho da vinculaçã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 suportam a leitura de valores de configuração atualizados.

A diferença entre IOptionsMonitor e IOptionsSnapshot é que:

  • IOptionsMonitor é um serviço Singleton que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências singleton.
  • IOptionsSnapshot é um serviço com escopo e fornece um instantâneo das opções no momento em que o IOptionsSnapshot<T> objeto é 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>o .

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 se liga contra:

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 início do aplicativo são lidas.

IOptionsMonitor

O código a seguir registra uma instância de configuração que MyOptions se liga contra.

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 o início do aplicativo são lidas.

Especifique um nome de chave personalizado para uma propriedade de configuração usando ConfigurationKeyName

Por padrão, os nomes de propriedade da classe options são usados como o nome da chave na fonte de configuração. Se o nome da propriedade for Title, o nome da chave na configuração também será Title .

Quando os nomes se diferenciam, você pode usar o ConfigurationKeyName atributo para especificar o nome da chave na fonte de configuração. Usando essa técnica, você pode mapear uma propriedade na configuração para uma no seu código com um nome diferente.

Isso é útil quando o nome da propriedade na fonte de configuração não é um identificador C# válido ou quando você deseja usar um nome diferente em seu código.

Por exemplo, considere a seguinte classe de opções:

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

    [ConfigurationKeyName("position-title")]
    public string Title { get; set; } = string.Empty;

    [ConfigurationKeyName("position-name")]
    public string Name { get; set; } = string.Empty;
}

As Title propriedades e Name class estão vinculadas ao position-title e position-name do seguinte appsettings.json arquivo:

{
  "Position": {
    "position-title": "Editor",
    "position-name": "Joe Smith"
  }
}

Suporte a opções nomeadas usando IConfigureNamedOptions

Opções nomeadas:

  • São úteis quando várias seções de configuração se ligam às mesmas propriedades.
  • Diferenciam maiúsculas de minúsculas.

Considere o seguinte appsettings.json arquivo:

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

Em vez de criar duas classes para vincular 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 exibe 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. IConfigureOptions<TOptions> as instâncias são tratadas como direcionadas à Options.DefaultName instância, que é string.Empty. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão do IOptionsFactory<TOptions> tem lógica para usar cada um adequadamente. A null opção nomeada é usada para direcionar todas as instâncias nomeadas em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll utilizar esta convenção.

OptionsBuilder API

OptionsBuilder<TOptions> é usado para configurar TOptions instâncias. OptionsBuilder simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada inicial AddOptions<TOptions>(string optionsName) , em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as ConfigureOptions sobrecargas que aceitam dependências de serviço só estão disponíveis via 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 serviços de DI para configurar opções

Os serviços podem ser acessados a partir da injeção de dependência durante a configuração de opções de duas maneiras:

  • Passe um delegado de configuração para Configure o OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fornece sobrecargas de Configure que permitem o uso de 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));
    
  • Crie um tipo que implemente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registre o tipo como um serviço.

Recomendamos passar um delegado de configuração para Configureo , já que a criação de um serviço é mais complexa. Criar um tipo é equivalente ao que a estrutura faz ao chamar Configure. A chamada Configure registra um genérico IConfigureNamedOptions<TOptions>transitório , que tem um construtor que aceita os tipos de serviço genéricos especificados.

Validação de opções

A validação de opções permite que os valores das opções sejam validados.

Considere o seguinte appsettings.json arquivo:

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

A classe a seguir é usada para vincular à "MyConfig" seção de DataAnnotations configuração e aplica algumas regras:

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:

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 ValidateDataAnnotations método de extensão é definido no pacote NuGet Microsoft.Extensions.Options.DataAnnotations. Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, esse pacote é referenciado implicitamente a partir 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 delegado:

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 seguinte classe 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 mover o código de validação de Program.cs e para uma classe.

Usando o código anterior, a validação é habilitada com Program.cs 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 suporta IValidatableObject. Para executar a validação em nível de classe de uma classe dentro da própria classe:

ValidateOnStart

A validação de opções é executada na primeira vez que uma TOption instância é criada. Isso significa, por exemplo, quando o primeiro acesso a IOptionsSnapshot<TOptions>.Value ocorre em um pipeline de solicitação ou quando IOptionsMonitor<TOptions>.Get(string) é chamado nas configurações presentes. Depois que as configurações são recarregadas, a validação é executada novamente. O tempo de execução do ASP.NET Core usa OptionsCache<TOptions> para armazenar em cache a instância de opções depois que ela é criada.

Para executar a validação de opções ansiosamente, quando o aplicativo for iniciado, ligue ValidateOnStart<TOptions>(OptionsBuilder<TOptions>)para Program.cs:

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Opções pós-configuração

Defina a pós-configuração com IPostConfigureOptions<TOptions>o . A pós-configuração é executada depois que toda a IConfigureOptions<TOptions> configuração ocorre:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure está disponível para opções nomeadas pós-configuração:

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 em Program.cs

Para aceder IOptions<TOptions> ou entrar no IOptionsMonitor<TOptions>, ligue Program.cs paraGetRequiredServiceWebApplication.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 adere a dois princípios importantes de engenharia de software:

  • Encapsulamento:
    • As classes que dependem das definições de configuração dependem apenas das definições de configuração que utilizam.
  • Separação de preocupações:
    • As configurações de diferentes partes do aplicativo não são dependentes ou acopladas umas às outras.

As opções também fornecem um mecanismo para validar dados de configuração. Para obter mais informações, consulte a seção Validação de opções.

Este 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.

Vincular configuração hierárquica

A maneira preferida 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 PositionOptions classe:

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 e ter um construtor público sem parâmetros.
  • Todas as propriedades públicas de leitura-gravação do tipo são vinculadas.
  • Os campos não estão vinculados. No código anterior, Position não está vinculado. O Position campo é usado para que a cadeia de caracteres "Position" não precise ser codificada no aplicativo ao vincular a classe a um provedor de configuração.

O seguinte código:

  • Chama ConfigurationBinder.Bind para vincular a PositionOptions classe à Position seção.
  • Exibe os dados de Position configuração.
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 o início do aplicativo são lidas.

ConfigurationBinder.Get<T> Vincula 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 PositionOptions classe:

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 o início do aplicativo são lidas.

Uma abordagem alternativa ao usar o padrão de opções é vincular a Position seção e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions é adicionado ao contêiner de serviço com Configure e vinculado à 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 ficheiro de configuração JSON após o início da aplicação não são lidas. Para ler as alterações após o início do aplicativo, use IOptionsSnapshot.

Opções de interfaces

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Os cenários pós-configuração permitem definir ou alterar opções depois que toda a IConfigureOptions<TOptions> configuração ocorre.

IOptionsFactory<TOptions> é responsável pela criação de novas instâncias de opções. Tem um único Create método. A implementação padrão leva todos os registrados IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> executa todas as configurações primeiro, seguido pela pós-configuração. Ele distingue entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> só chama a interface apropriada.

IOptionsMonitorCache<TOptions> é usado por para armazenar instâncias em IOptionsMonitor<TOptions> cache TOptions . O IOptionsMonitorCache<TOptions> invalida instâncias de opções no monitor para que o valor seja recalculado (TryRemove). Os valores podem ser introduzidos manualmente com TryAdd. O Clear método é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.

Use IOptionsSnapshot para ler dados atualizados

Utilização de 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 de desempenho significativa porque é um serviço com escopo e é recalculado por solicitação. Para obter mais informações, consulte este problema do GitHub e Melhorar o desempenho da vinculaçã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 suportam a leitura de valores de configuração atualizados.

A diferença entre IOptionsMonitor e IOptionsSnapshot é que:

  • IOptionsMonitor é um serviço Singleton que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências singleton.
  • IOptionsSnapshot é um serviço com escopo e fornece um instantâneo das opções no momento em que o IOptionsSnapshot<T> objeto é 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>o .

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 se liga contra:

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 início do aplicativo são lidas.

IOptionsMonitor

O código a seguir registra uma instância de configuração que MyOptions se liga contra.

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 o início do aplicativo são lidas.

Suporte a opções nomeadas usando IConfigureNamedOptions

Opções nomeadas:

  • São úteis quando várias seções de configuração se ligam às mesmas propriedades.
  • Diferenciam maiúsculas de minúsculas.

Considere o seguinte appsettings.json arquivo:

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

Em vez de criar duas classes para vincular 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 exibe 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. IConfigureOptions<TOptions> as instâncias são tratadas como direcionadas à Options.DefaultName instância, que é string.Empty. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão do IOptionsFactory<TOptions> tem lógica para usar cada um adequadamente. A null opção nomeada é usada para direcionar todas as instâncias nomeadas em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll utilizar esta convenção.

OptionsBuilder API

OptionsBuilder<TOptions> é usado para configurar TOptions instâncias. OptionsBuilder simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada inicial AddOptions<TOptions>(string optionsName) , em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as ConfigureOptions sobrecargas que aceitam dependências de serviço só estão disponíveis via 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 serviços de DI para configurar opções

Os serviços podem ser acessados a partir da injeção de dependência durante a configuração de opções de duas maneiras:

  • Passe um delegado de configuração para Configure o OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fornece sobrecargas de Configure que permitem o uso de 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));
    
  • Crie um tipo que implemente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registre o tipo como um serviço.

Recomendamos passar um delegado de configuração para Configureo , já que a criação de um serviço é mais complexa. Criar um tipo é equivalente ao que a estrutura faz ao chamar Configure. A chamada Configure registra um genérico IConfigureNamedOptions<TOptions>transitório , que tem um construtor que aceita os tipos de serviço genéricos especificados.

Validação de opções

A validação de opções permite que os valores das opções sejam validados.

Considere o seguinte appsettings.json arquivo:

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

A classe a seguir é usada para vincular à "MyConfig" seção de DataAnnotations configuração e aplica algumas regras:

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:

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 ValidateDataAnnotations método de extensão é definido no pacote NuGet Microsoft.Extensions.Options.DataAnnotations. Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, esse pacote é referenciado implicitamente a partir 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 delegado:

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 seguinte classe 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 mover o código de validação de Program.cs e para uma classe.

Usando o código anterior, a validação é habilitada com Program.cs 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 suporta IValidatableObject. Para executar a validação em nível de classe de uma classe dentro da própria classe:

ValidateOnStart

A validação de opções é executada na primeira vez que uma IOptions<TOptions>implementação , IOptionsSnapshot<TOptions>ou IOptionsMonitor<TOptions> é criada. Para executar a validação de opções ansiosamente, quando o aplicativo for iniciado, ligue ValidateOnStart para Program.cs:

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Opções pós-configuração

Defina a pós-configuração com IPostConfigureOptions<TOptions>o . A pós-configuração é executada depois que toda a IConfigureOptions<TOptions> configuração ocorre:

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure está disponível para opções nomeadas pós-configuração:

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 em Program.cs

Para aceder IOptions<TOptions> ou entrar no IOptionsMonitor<TOptions>, ligue Program.cs paraGetRequiredServiceWebApplication.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 adere a dois princípios importantes de engenharia de software:

  • Encapsulamento:
    • As classes que dependem das definições de configuração dependem apenas das definições de configuração que utilizam.
  • Separação de preocupações:
    • As configurações de diferentes partes do aplicativo não são dependentes ou acopladas umas às outras.

As opções também fornecem um mecanismo para validar dados de configuração. Para obter mais informações, consulte a seção Validação de opções.

Este 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.

Visualizar ou descarregar amostra de código (como descarregar)

Vincular configuração hierárquica

A maneira preferida 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 PositionOptions classe:

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 abstrato e ter um construtor público sem parâmetros.
  • Todas as propriedades públicas de leitura-gravação do tipo são vinculadas.
  • Os campos não estão vinculados. No código anterior, Position não está vinculado. A Position propriedade é usada para que a cadeia de caracteres "Position" não precise ser codificada no aplicativo ao vincular a classe a um provedor de configuração.

O seguinte código:

  • Chama ConfigurationBinder.Bind para vincular a PositionOptions classe à Position seção.
  • Exibe os dados de Position configuração.
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 o início do aplicativo são lidas.

ConfigurationBinder.Get<T> Vincula 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 PositionOptions classe:

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 o início do aplicativo são lidas.

Uma abordagem alternativa ao usar o padrão de opções é vincular a Position seção e adicioná-la ao contêiner do serviço de injeção de dependência. No código a seguir, PositionOptions é adicionado ao contêiner de serviço com Configure e vinculado à 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 ficheiro de configuração JSON após o início da aplicação não são lidas. Para ler as alterações após o início do aplicativo, use IOptionsSnapshot.

Opções de interfaces

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Os cenários pós-configuração permitem definir ou alterar opções depois que toda a IConfigureOptions<TOptions> configuração ocorre.

IOptionsFactory<TOptions> é responsável pela criação de novas instâncias de opções. Tem um único Create método. A implementação padrão leva todos os registrados IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> executa todas as configurações primeiro, seguido pela pós-configuração. Ele distingue entre IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> só chama a interface apropriada.

IOptionsMonitorCache<TOptions> é usado por para armazenar instâncias em IOptionsMonitor<TOptions> cache TOptions . O IOptionsMonitorCache<TOptions> invalida instâncias de opções no monitor para que o valor seja recalculado (TryRemove). Os valores podem ser introduzidos manualmente com TryAdd. O Clear método é usado quando todas as instâncias nomeadas devem ser recriadas sob demanda.

Use IOptionsSnapshot para ler dados atualizados

Usando IOptionsSnapshot<TOptions>o , 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. As alterações na configuração são lidas depois que o aplicativo é iniciado ao usar provedores de configuração que suportam a leitura de valores de configuração atualizados.

A diferença entre IOptionsMonitor e IOptionsSnapshot é que:

  • IOptionsMonitor é um serviço Singleton que recupera valores de opção atuais a qualquer momento, o que é especialmente útil em dependências singleton.
  • IOptionsSnapshot é um serviço com escopo e fornece um instantâneo das opções no momento em que o IOptionsSnapshot<T> objeto é 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>o .

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 se liga contra:

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 início do aplicativo são lidas.

IOptionsMonitor

O código a seguir registra uma instância de configuração que MyOptions se liga contra.

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 o início do aplicativo são lidas.

Suporte a opções nomeadas usando IConfigureNamedOptions

Opções nomeadas:

  • São úteis quando várias seções de configuração se ligam às mesmas propriedades.
  • Diferenciam maiúsculas de minúsculas.

Considere o seguinte appsettings.json arquivo:

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

Em vez de criar duas classes para vincular 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 exibe 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. IConfigureOptions<TOptions> as instâncias são tratadas como direcionadas à Options.DefaultName instância, que é string.Empty. IConfigureNamedOptions<TOptions> também implementa IConfigureOptions<TOptions>. A implementação padrão do IOptionsFactory<TOptions> tem lógica para usar cada um adequadamente. A null opção nomeada é usada para direcionar todas as instâncias nomeadas em vez de uma instância nomeada específica. ConfigureAll e PostConfigureAll utilizar esta convenção.

OptionsBuilder API

OptionsBuilder<TOptions> é usado para configurar TOptions instâncias. OptionsBuilder simplifica a criação de opções nomeadas, pois é apenas um único parâmetro para a chamada inicial AddOptions<TOptions>(string optionsName) , em vez de aparecer em todas as chamadas subsequentes. A validação de opções e as ConfigureOptions sobrecargas que aceitam dependências de serviço só estão disponíveis via 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 serviços de DI para configurar opções

Os serviços podem ser acessados a partir da injeção de dependência durante a configuração de opções de duas maneiras:

  • Passe um delegado de configuração para Configure o OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fornece sobrecargas de Configure que permitem o uso de 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));
    
  • Crie um tipo que implemente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> e registre o tipo como um serviço.

Recomendamos passar um delegado de configuração para Configureo , já que a criação de um serviço é mais complexa. Criar um tipo é equivalente ao que a estrutura faz ao chamar Configure. A chamada Configure registra um genérico IConfigureNamedOptions<TOptions>transitório , que tem um construtor que aceita os tipos de serviço genéricos especificados.

Validação de opções

A validação de opções permite que os valores das opções sejam validados.

Considere o seguinte appsettings.json arquivo:

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

A classe a seguir se liga à "MyConfig" seção de DataAnnotations configuração e aplica algumas regras:

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:

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 ValidateDataAnnotations método de extensão é definido no pacote NuGet Microsoft.Extensions.Options.DataAnnotations. Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, esse pacote é referenciado implicitamente a partir 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 delegado:

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 seguinte classe 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 mover o código de validação de StartUp e para uma classe.

Usando o código anterior, a validação é habilitada com Startup.ConfigureServices 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();
}

Opções pós-configuração

Defina a pós-configuração com IPostConfigureOptions<TOptions>o . A pós-configuração é executada depois que toda a IConfigureOptions<TOptions> configuração ocorre:

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

PostConfigure está disponível para opções nomeadas pós-configuração:

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";
});

Aceder a opções durante o arranque

IOptions<TOptions> e IOptionsMonitor<TOptions> pode ser usado no Startup.Configure, uma vez que os serviços são criados antes da execução do Configure método.

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

Não use IOptions<TOptions> ou IOptionsMonitor<TOptions> em Startup.ConfigureServices. Um estado de opções inconsistente pode existir devido à ordem de registros de serviço.

Options.ConfigurationExtensions pacote NuGet

O pacote Microsoft.Extensions.Options.ConfigurationExtensions é implicitamente referenciado em aplicativos ASP.NET Core.