Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

O Padrão de Opções em C#

[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]

A configuração em aplicações .NET geralmente reside em appsettings.json, variáveis de ambiente ou segredos do usuário. Extrair esses dados de configuração de um arquivo e inseri-los nas suas classes de uma maneira limpa e testável é o que o padrão de opções resolve. Em vez de ler manualmente as chaves JSON ou passar strings brutas, você vincula uma seção de configuração a uma classe C# fortemente tipada e permite que a injeção de dependência a entregue onde for necessário.

Em seu vídeo "The Options Pattern in C#", Tim Corey introduz as três variações do padrão de opções (IOptions, IOptionsSnapshot, e IOptionsMonitor), demonstra cada uma em um aplicativo de servidor Blazor, e mostra como adicionar validação para que sua aplicação falhe rapidamente quando falta configuração ou está malformada. Se você está construindo qualquer projeto .NET que leia de arquivos de configuração e use injeção de dependência, este padrão é fundamental.

A Configuração: Um Modelo POCO e appsettings.json

[0:34 - 1:46] Tim começa com um aplicativo Blazor (renderizado no lado do servidor, sem interatividade no lado do cliente) que já tem duas partes em funcionamento. A primeira é uma seção em appsettings.json com três pares de chave-valor sob uma seção CloudInfo:

// appsettings.json
{
  "CloudInfo": {
    "Storage": "https://storage.example.com",
    "Website": "https://www.example.com",
    "API": "https://api.example.com"
  }
}
// appsettings.json
{
  "CloudInfo": {
    "Storage": "https://storage.example.com",
    "Website": "https://www.example.com",
    "API": "https://api.example.com"
  }
}

A segunda peça é uma classe simples em C# (um POCO) cujas propriedades espelham as chaves JSON:

public class CloudInfoOptions
{
    public string Storage { get; set; }
    public string Website { get; set; }
    public string API { get; set; }
}
public class CloudInfoOptions
{
    public string Storage { get; set; }
    public string Website { get; set; }
    public string API { get; set; }
}

Tim observa que a fonte de configuração não precisa ser appsettings.json especificamente. Pode ser appsettings.Development.json, secrets.json, ou qualquer combinação. O padrão de opções lê de qualquer provedor de configuração registrado no aplicativo, e os nomes das propriedades no POCO correspondem às chaves JSON por convenção.

Registrando Opções em Program.cs

[2:25 - 3:15] Conectar a configuração à injeção de dependência exige duas chamadas de método em Program.cs:

// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo");
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo");

AddOptions<t>() registra o tipo com DI. BindConfiguration("CloudInfo") informa ao framework qual seção da configuração mapear. A string "CloudInfo" corresponde à chave JSON em appsettings.json. Se o nome da seção e o nome da classe não corresponderem, o argumento de string é o que a estrutura usa para encontrar os dados corretos.

IOptions: A Abordagem Singleton

[3:15 - 5:18] Com o registro no lugar, Tim injeta a configuração em uma página Blazor usando IOptions<CloudInfoOptions>:

@inject IOptions<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        var options = CloudConfig.Value;
        // options.Storage, options.Website, options.API are available
    }
}
@inject IOptions<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        var options = CloudConfig.Value;
        // options.Storage, options.Website, options.API are available
    }
}

O detalhe crítico é que IOptions<t> está registrado como um singleton. Os valores de configuração são lidos uma vez quando o aplicativo inicia e armazenados em cache pela vida útil do processo. Se você mudar appsettings.json enquanto o aplicativo está em execução, IOptions não refletirá essas mudanças.

Para a maioria dos aplicativos, essa é a escolha certa. A configuração raramente muda em tempo de execução, e o tempo de vida como singleton significa zero sobrecarga por solicitação.

IOptionsSnapshot: Recarregamento Controlado

[5:18 - 6:55] Tim troca IOptions por IOptionsSnapshot para demonstrar a variante com escopo:

@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig

IOptionsSnapshot<t> lê a configuração novamente para cada solicitação (tempo de vida com escopo). Se você modificar appsettings.json enquanto o aplicativo está em execução, a próxima solicitação web captará os novos valores. A solicitação anterior mantém seu próprio snapshot, então não há inconsistência no meio da solicitação.

Isso é útil para aplicativos onde as mudanças de configuração precisam entrar em vigor sem reiniciar, como ativar/desativar flags de função, atualizar endpoints de API ou rodar strings de conexão de armazenamento. A troca é um pequeno custo por solicitação para reler a configuração, o qual é insignificante para a maioria das cargas de trabalho.

IOptionsMonitor: Notificações de Mudança ao Vivo

[6:55 - 8:34] A terceira variante, IOptionsMonitor<t>, vai além do recarregamento de instantâneos. Ele monitora ativamente as mudanças de configuração e pode disparar callbacks quando os valores são atualizados:

@inject IOptionsMonitor<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        // Current values
        var current = CloudConfig.CurrentValue;

        // Register a callback for live changes
        CloudConfig.OnChange(updatedOptions =>
        {
            // React to configuration changes in real time
        });
    }
}
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig

@code {
    protected override void OnInitialized()
    {
        // Current values
        var current = CloudConfig.CurrentValue;

        // Register a callback for live changes
        CloudConfig.OnChange(updatedOptions =>
        {
            // React to configuration changes in real time
        });
    }
}

Ao contrário de IOptionsSnapshot, que atualiza apenas entre solicitações, IOptionsMonitor pode detectar mudanças durante uma operação de longa duração. Tim aponta que isso é mais relevante para serviços de fundo, hubs SignalR ou qualquer componente com um tempo de vida mais longo que uma única solicitação HTTP.

Adicionando Validação

[8:34 - 10:05] The final piece Tim covers is validation. O padrão de opções suporta regras de validação inline que são executadas quando a configuração é acessada pela primeira vez:

builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo")
    .Validate(opts =>
        !string.IsNullOrEmpty(opts.Storage) &&
        !string.IsNullOrEmpty(opts.Website) &&
        !string.IsNullOrEmpty(opts.API));
builder.Services.AddOptions<CloudInfoOptions>()
    .BindConfiguration("CloudInfo")
    .Validate(opts =>
        !string.IsNullOrEmpty(opts.Storage) &&
        !string.IsNullOrEmpty(opts.Website) &&
        !string.IsNullOrEmpty(opts.API));

Tim demonstra o caso de falha removendo o valor Storage de appsettings.json e lançando o aplicativo. O resultado é uma exceção não tratada imediata: "CloudInfoOptions falhou na validação." O aplicativo se recusa a iniciar com a configuração incompleta, o que é exatamente o comportamento que você deseja. Descobrir um valor ausente na inicialização é muito melhor do que atingir uma referência nula em produção às 2 da manhã.

Para cenários de validação mais complexos (verificações entre propriedades, regras condicionais), você pode encadear múltiplas chamadas .Validate() ou implementar IValidateOptions<t> como uma classe de validação dedicada.

Concluindo: Três Interfaces, Um Padrão

[10:05 - 10:10] O padrão de opções fornece uma separação clara entre armazenamento e consumo de configuração. IOptions cobre a maioria dos casos de uso onde a configuração é estática. IOptionsSnapshot lida com aplicativos que precisam captar mudanças entre solicitações. IOptionsMonitor serve componentes de longa execução que necessitam de conhecimento em tempo real das atualizações de configuração. E a validação garante que seu aplicativo falhe ruidosamente quando valores obrigatórios estão ausentes.

Conclusão

[10:10 - 10:15] Para resumir: registre sua configuração com AddOptions<t>().BindConfiguration("SectionName") em Program.cs, injete uma das três interfaces (IOptions, IOptionsSnapshot, ou IOptionsMonitor) dependendo dos seus requisitos de recarregamento, e adicione .Validate() para capturar valores faltantes no início ao invés de em tempo de execução.

O padrão funciona em qualquer tipo de projeto .NET: Blazor, ASP.NET Core, aplicativos de console, serviços de trabalho. O registro é o mesmo em todos os lugares.

Dica de exemplo: Se você não tem certeza de qual interface usar, comece com IOptions<t>. É o mais simples, não tem sobrecarga por solicitação e cobre a vasta maioria dos casos onde a configuração é definida na inicialização e não muda. Só mude para IOptionsSnapshot ou IOptionsMonitor quando você tiver uma necessidade concreta de recarregamento em tempo de execução.

Assista o vídeo completo vídeo em seu Canal no YouTube e obtenha mais insights sobre padrões de configuração em C#.

Hero Worlddot related to O Padrão de Opções em C#
Hero Affiliate related to O Padrão de Opções em C#

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de Suporte Iron

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim