Padrões de Configuração do Microsoft.Extensions
Quando Usar Esta Skill
Use esta skill quando:
- Vincular configuração de appsettings.json para classes fortemente tipadas
- Validar configuração na inicialização da aplicação (fail fast)
- Implementar lógica de validação complexa para configurações
- Projetar classes de configuração que sejam testáveis e sustentáveis
- Compreender IOptions, IOptionsSnapshot e IOptionsMonitor
Arquivos de Referência
- advanced-patterns.md: Validadores com dependências, opções nomeadas, exemplo completo de produção (AkkaSettings) e teste de validadores
Por que a Validação de Configuração é Importante
O Problema: Aplicações frequentemente falham em tempo de execução devido a configurações incorretas - strings de conexão ausentes, URLs inválidos, valores fora do intervalo. Essas falhas ocorrem profundamente na lógica de negócio, longe de onde a configuração é carregada.
A Solução: Validar a configuração na inicialização. Se for inválida, falhe imediatamente com uma mensagem de erro clara.
// RUIM: Falha em tempo de execução quando alguém tenta usar o serviço
public class EmailService
{
public EmailService(IOptions<SmtpSettings> options)
{
var settings = options.Value;
// Lança NullReferenceException 10 minutos após a produção
_client = new SmtpClient(settings.Host, settings.Port);
}
}
// BOM: Falha na inicialização com erro claro
// "Validação de SmtpSettings falhou: Host é obrigatório"
Padrão 1: Vinculação Básica de Opções
Definir uma Classe de Configurações
public class SmtpSettings
{
public const string SectionName = "Smtp";
public string Host { get; set; } = string.Empty;
public int Port { get; set; } = 587;
public string? Username { get; set; }
public string? Password { get; set; }
public bool UseSsl { get; set; } = true;
}
Vincular a partir da Configuração
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName);
// appsettings.json
{
"Smtp": {
"Host": "smtp.exemplo.com",
"Port": 587,
"Username": "[email protected]",
"Password": "secreto",
"UseSsl": true
}
}
Consumir em Serviços
public class EmailService
{
private readonly SmtpSettings _settings;
// IOptions<T> - singleton, lido uma vez na inicialização
public EmailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
Padrão 2: Validação com Data Annotations
Para regras de validação simples, use Data Annotations:
using System.ComponentModel.DataAnnotations;
public class SmtpSettings
{
public const string SectionName = "Smtp";
[Required(ErrorMessage = "Host SMTP é obrigatório")]
public string Host { get; set; } = string.Empty;
[Range(1, 65535, ErrorMessage = "Porta deve estar entre 1 e 65535")]
public int Port { get; set; } = 587;
[EmailAddress(ErrorMessage = "Username deve ser um endereço de e-mail válido")]
public string? Username { get; set; }
public string? Password { get; set; }
public bool UseSsl { get; set; } = true;
}
Habilitar Validação com Data Annotations
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName)
.ValidateDataAnnotations() // Habilita validação baseada em atributos
.ValidateOnStart(); // Valida imediatamente na inicialização
Ponto Chave: .ValidateOnStart() é crítico. Sem ele, a validação só é executada quando as opções são acessadas pela primeira vez.
Padrão 3: IValidateOptions para Validação Complexa
Data Annotations funcionam para regras simples, mas a validação complexa requer IValidateOptions<T>:
| Cenário | Data Annotations | IValidateOptions |
|---|---|---|
| Campo obrigatório | Sim | Sim |
| Verificação de intervalo | Sim | Sim |
| Validação entre propriedades | Não | Sim |
| Validação condicional | Não | Sim |
| Verificações de serviço externo | Não | Sim |
| Injeção de dependência no validador | Não | Sim |
Implementando IValidateOptions
using Microsoft.Extensions.Options;
public class SmtpSettingsValidator : IValidateOptions<SmtpSettings>
{
public ValidateOptionsResult Validate(string? name, SmtpSettings options)
{
var failures = new List<string>();
if (string.IsNullOrWhiteSpace(options.Host))
failures.Add("Host é obrigatório");
if (options.Port is < 1 or > 65535)
failures.Add($"Porta {options.Port} é inválida. Deve estar entre 1 e 65535");
// Validação entre propriedades
if (!string.IsNullOrEmpty(options.Username) && string.IsNullOrEmpty(options.Password))
failures.Add("Senha é obrigatória quando Nome de Usuário é especificado");
// Validação condicional
if (options.UseSsl && options.Port == 25)
failures.Add("Porta 25 normalmente não é usada com SSL. Considere a porta 465 ou 587");
return failures.Count > 0
? ValidateOptionsResult.Fail(failures)
: ValidateOptionsResult.Success;
}
}
Registrar o Validador
builder.Services.AddOptions<SmtpSettings>()
.BindConfiguration(SmtpSettings.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.AddSingleton<IValidateOptions<SmtpSettings>, SmtpSettingsValidator>();
A ordem importa: Data Annotations são executados primeiro, depois os validadores IValidateOptions. Todas as falhas são coletadas juntas.
Veja advanced-patterns.md para validadores com dependências, opções nomeadas e um exemplo completo de produção.
Padrão 4: Ciclo de Vida das Opções
| Interface | Ciclo de Vida | Recarrega com Mudanças | Caso de Uso |
|---|---|---|---|
IOptions<T> | Singleton | Não | Configuração estática, lida uma vez |
IOptionsSnapshot<T> | Escopo | Sim (por requisição) | Aplicações web que precisam de configuração atualizada |
IOptionsMonitor<T> | Singleton | Sim (com callback) | Serviços em segundo plano, atualizações em tempo real |
IOptionsMonitor para Serviços em Segundo Plano
public class BackgroundWorker : BackgroundService
{
private readonly IOptionsMonitor<WorkerSettings> _optionsMonitor;
private WorkerSettings _currentSettings;
public BackgroundWorker(IOptionsMonitor<WorkerSettings> optionsMonitor)
{
_optionsMonitor = optionsMonitor;
_currentSettings = optionsMonitor.CurrentValue;
_optionsMonitor.OnChange(settings =>
{
_currentSettings = settings;
});
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await DoWorkAsync();
await Task.Delay(_currentSettings.PollingInterval, stoppingToken);
}
}
}
Padrão 5: Pós-Configuração
Modifique as opções após a vinculação, mas antes da validação:
builder.Services.AddOptions<ApiSettings>()
.BindConfiguration("Api")
.PostConfigure(options =>
{
if (!string.IsNullOrEmpty(options.BaseUrl) && !options.BaseUrl.EndsWith('/'))
options.BaseUrl += '/';
options.Timeout ??= TimeSpan.FromSeconds(30);
})
.ValidateDataAnnotations()
.ValidateOnStart();
Anti-Padrões a Evitar
1. Acesso Manual à Configuração
// RUIM: Ignora validação, difícil de testar
public class MyService
{
public MyService(IConfiguration configuration)
{
var host = configuration["Smtp:Host"]; // Sem validação!
}
}
// BOM: Fortemente tipado, validado
public class MyService
{
public MyService(IOptions<SmtpSettings> options)
{
var host = options.Value.Host; // Validado na inicialização
}
}
2. Validação no Construtor
// RUIM: Validação acontece em tempo de execução, não na inicialização
public class MyService
{
public MyService(IOptions<Settings> options)
{
if (string.IsNullOrEmpty(options.Value.Required))
throw new ArgumentException("Obrigatório está ausente"); // Tarde demais!
}
}
// BOM: Validação na inicialização via IValidateOptions + ValidateOnStart()
3. Esquecer o ValidateOnStart
// RUIM: Validação só é executada quando acessada pela primeira vez
builder.Services.AddOptions<Settings>()
.ValidateDataAnnotations(); // Faltou ValidateOnStart!
// BOM: Falha imediatamente se inválido
builder.Services.AddOptions<Settings>()
.ValidateDataAnnotations()
.ValidateOnStart();
4. Lançar Exceção no IValidateOptions
// RUIM: Lança exceção, quebra a cadeia de validação
public ValidateOptionsResult Validate(string? name, Settings options)
{
if (options.Value < 0)
throw new ArgumentException("Valor não pode ser negativo"); // Errado!
return ValidateOptionsResult.Success;
}
// BOM: Retorna resultado de falha
public ValidateOptionsResult Validate(string? name, Settings options)
{
if (options.Value < 0)
return ValidateOptionsResult.Fail("Valor não pode ser negativo");
return ValidateOptionsResult.Success;
}
Resumo
| Princípio | Implementação |
|---|---|
| Falhar rápido | .ValidateOnStart() |
| Fortemente tipado | Vincular a classes POCO |
| Validação simples | Data Annotations |
| Validação complexa | IValidateOptions<T> |
| Regras entre propriedades | IValidateOptions<T> |
| Ciente de ambiente | Injetar IHostEnvironment |
| Testável | Validadores são classes simples |