Configurações dinâmicas com Options Pattern

1 de maio de 2022
Bryan Lima

Coder and Tech Consultant

  • Twitter
  • Facebook
  • LinkedIn
IOptions, IOptionsSnapshot, IOptionsMonitor

Às vezes é necessário obter as configurações de forma dinâmica para a aplicação, baseado no tipo de ambiente que vai executar, como produção ou desenvolvimento, ou até mesmo alterando seu comportamento durante a execução da aplicação.

Para começar será feita uma breve introdução e configuração que será utilizada durante todo o artigo.

IConfiguration

Representa os valores das fontes de dados no formato chave-valor que podem ser de vários formatos como yaml, json, xml, variáveis de ambiente, banco de dados, api, etc.

Configurando a aplicação

Instale a extensão Microsoft.Extensions.Configuration

Adicione as chaves abaixo no arquivo appsettings.json.

JSON
"Paginas": {
  "Servicos": {
    "Titulo": "Serviços",
    "Ativo": true
  }
}

Crie o arquivo ServicosConfig.cs e adicione o código abaixo nele.

C#
public class ServicosConfig
{
	public string? Titulo { get; set; }
	public bool Ativo { get; set; }
}

Adicione no arquivo Program.cs, ou no antigo arquivo Startup.cs, a linha abaixo para obter a configuração por seção do arquivo appsettings.json.

C#
builder.Services.Configure<ServicosConfig>(builder.Configuration.GetSection("Paginas:Servicos"));

IOptions

É registrado como Singleton e utilizado durante todo o ciclo de vida da aplicação, não podendo ler as atualizações depois de inicializado.

C#
// Controller ...
[HttpGet]
[Route("/ioptions")]
public void OperacaoComIOptions([FromServices] IOptions<ServicosConfig> options)
{
    ServicosConfig cfg = options.Value;
    // …
}

Obtendo valores sem parar ou recompilar a aplicação

As interfaces IOptionsSnapshot<T> e IOptionsMonitor<T> observam alterações na fonte de dados da configuração para manter atualizados na aplicação, mesmo depois de inicializadas.

Como visto com IOptions é necessário parar ou recompilar a aplicação para as configurações serem atualizadas.

IOptionsSnapshot

Faz uma cópia atualizada das configurações para disponibilizar durante as solicitações. Isso significa que se a configuração for alterada, a próxima atualização estará disponível na solicitação seguinte e não em tempo real durante uma operação.

C#
// Controller …
[HttpGet]
[Route("/snapshot")]
public void OperacaoComIOptionsSnapshot([FromServices] IOptionsSnapshot<ServicosConfig> options)
{
    ServicosConfig cfg = options.Value;

    // …
}

Faça o seguinte para ver como funciona:

  1. Coloque um breakpoint antes de acessar o valor da configuração;
  2. Altere e salve o valor no appsettings.json;

Repare que o valor não é atualizado durante a requisição, mas se fizer uma nova requisição o valor estará atualizado.

IOptionsMonitor

É registrado como Singleton, isso significa que se o valor for alterado mesmo que manualmente isso terá efeito para todo os outros locais que utilizam, mas a grande diferença é que IOptionsMonitor dispara um evento em tempo real quando existe atualização das configurações, e o desenvolvedor pode escolher entre usar a configuração antiga ou a nova durante a execução de uma operação.

É útil para usar valores atualizados a qualquer momento ou em operações de longa duração onde nem todos os valores são utilizados no começo da operação.

Um cenário que dá para utilizar IOptionsMonitor é em Flag-Feature quando uma funcionalidade é desativada no meio da operação e nisso pode disparar o CancellationTokenSource para interromper a operação em execução.

C#
[HttpGet]
[Route("/longaduracao")]
public async Task LongaDuracao([FromServices] IOptionsMonitor<ServicosConfig> options)
{
    ServicosConfig cfg = options.CurrentValue;
    var cts = new CancellationTokenSource();

    // Registra para receber notificações de atualizações
    options.OnChange((novaConfig, s) =>
    {
        // Atualiza a configuração APENAS do método LongaDuracao.
        cfg = novaConfig;

        // Verifica a situação da funcionalidade
        if (cfg.Ativo == false)
        {
            // Notifica para cancelar a execução da operação.
            cts.Cancel();
        }
    });


    if (cfg.Ativo)
    {
        // Simula uma execução demorada.
        await Task.Delay(delay: TimeSpan.FromHours(1), cts.Token);
    }

    // …
}
PUBLICADO EM: