← ASP.NET Core: aplicações, contratos e operação
02 / 10 · 60 MIN

Dependências, ciclos de vida e configuração

Relaciona o âmbito das dependências com o trabalho que executam e determina de onde vem a configuração efetiva.

Uma instância para que unidade

Transient cria uma instância por resolução; scoped conserva a instância dentro do âmbito; singleton partilha a instância no contentor. Nas APIs HTTP consideradas aqui, o pedido tem um âmbito. Dois consumidores que resolvem o mesmo registo scoped nesse pedido recebem a mesma instância, desde que não se criem âmbitos adicionais.

O âmbito não é a thread: processamento assíncrono pode continuar noutra thread. Também não é uma regra universal para interfaces de utilizador com circuitos persistentes. RequestStamp demonstra apenas identidade dentro do pedido. Um singleton com estado mutável partilhado precisa de uma análise própria de concorrência.

Trabalho fora do pedido

Um BackgroundService é registado como singleton e não recebe automaticamente um âmbito por trabalho. Para usar um repositório scoped, pode receber IServiceScopeFactory, criar um âmbito por unidade, resolver o repositório nesse âmbito e libertá-lo quando a unidade terminar. A operação deve terminar antes da libertação das dependências.

Resolver scoped no provider raiz, com validação desativada, pode prolongar a sua vida até ao encerramento do contentor. Desativar a validação não corrige essa fronteira. Se as dependências exigirem libertação assíncrona, o desenho deve também respeitar esse contrato; não basta guardar o provider de um pedido já terminado.

A configuração tem precedência

Com CreateBuilder(args) e os providers predefinidos, os argumentos da linha de comandos têm prioridade sobre variáveis de ambiente, que têm prioridade sobre os ficheiros JSON da aplicação. O ficheiro específico do ambiente sobrepõe-se a appsettings.json. Em Development, user secrets também entram na composição predefinida, abaixo das variáveis de ambiente.

Para Delivery:BatchSize, JSON=20, ambiente=25 e argumento=60 produzem 60. Alterar apenas o JSON não corrige esse valor. A conclusão depende da composição declarada: acrescentar um provider no fim pode mudar a prioridade. Não transformes a ordem predefinida numa regra absoluta para qualquer host personalizado.

Nomes hierárquicos e prefixos

No provider de ambiente, Delivery__BatchSize representa Delivery:BatchSize. O duplo underscore converte-se no separador hierárquico e evita depender do suporte do sistema a dois pontos em nomes de variáveis. Um underscore simples ou um ponto não exprime a mesma transformação.

AddEnvironmentVariables(prefix: "OPS_") filtra as variáveis pelo prefixo e retira-o da chave obtida. Se for acrescentado depois de CreateBuilder, OPS_Delivery__BatchSize=12 pode sobrepor-se ao argumento predefinido 25. O prefixo delimita a origem; não cria obrigatoriamente uma secção OPS nem reduz a prioridade do provider.

Ler, conservar e atualizar opções

IOptions fornece uma visão que não oferece recarga após o arranque. IOptionsSnapshot é scoped: calcula e conserva opções no âmbito, permitindo novas leituras em pedidos posteriores quando a origem suporta recarga. IOptionsMonitor é singleton e oferece valores atuais e notificações de alteração. Escolhe pela necessidade do consumidor e pelo seu ciclo de vida.

Uma origem que não recarrega não ganha essa capacidade apenas por mudar a interface. E copiar campos de CurrentValue uma vez para um objeto próprio não liga automaticamente esse objeto às alterações. É preciso definir a leitura ou atualização do consumidor, incluindo a coerência de uma operação que usa vários valores.

Aceitar configuração no momento certo

Bind associa valores à classe de opções; não prova que os valores sejam aceitáveis. No exemplo, Range(1,100) declara o intervalo, ValidateDataAnnotations ativa a validação dessas anotações e ValidateOnStart exige a sua avaliação no arranque. Sem esse último passo, a validação pode ficar para o primeiro acesso às opções.

BatchSize=0 viola o contrato mesmo que seja um inteiro convertível. O valor inicial zero também torna a omissão inválida neste exemplo. Configuração válida não prova capacidade real de entrega, acesso a uma base de dados ou prontidão de todas as dependências. São critérios adicionais, avaliados nas suas próprias fronteiras.

Confrontar o modelo com a evidência

Lê Program.cs com Delivery__BatchSize=25 e sem outra fonte vencedora. O endpoint recebe opções scoped e RequestStamp scoped, e devolve apenas o valor não secreto e o identificador ilustrativo. Não é um endpoint para publicar toda a configuração da aplicação. O mesmo pedido conserva a instância; pedidos diferentes não devem partilhar esse objeto scoped.

Agora considera dois incidentes separados: um argumento antigo mantém BatchSize=60 apesar de o ambiente indicar 25; um worker singleton captura um repositório scoped. O primeiro exige corrigir a fonte efetiva. O segundo exige rever a fronteira da unidade de trabalho. As perguntas avaliam a escolha dessas decisões sem executar o exemplo.

// Program.cs: ASP.NET Core 10.0 Web SDK, net10.0, ImplicitUsings enabled.
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<RequestStamp>();
builder.Services.AddOptions<DeliveryOptions>()
    .Bind(builder.Configuration.GetSection("Delivery"))
    .ValidateDataAnnotations()
    .ValidateOnStart();
var app = builder.Build();
app.MapGet("/configuration-preview",
    (IOptionsSnapshot<DeliveryOptions> options, RequestStamp stamp) =>
        Results.Ok(new { batchSize = options.Value.BatchSize, request = stamp.Id }));
app.Run();

public sealed class DeliveryOptions
{
    [Range(1, 100)]
    public int BatchSize { get; set; }
}
public sealed class RequestStamp
{
    public string Id { get; } = Guid.NewGuid().ToString("N");
}
NA PRÁTICA

Assume Delivery__BatchSize=25 e ausência de argumentos ou providers posteriores para essa chave. O valor passa Range(1,100). Zero, incluindo o valor inicial quando falta configuração, é rejeitado no arranque.

O endpoint só mostra um valor não secreto e um identificador ilustrativo; não entrega encomendas.

Armadilhas comuns

Capturar um serviço scoped num singleton; resolver scoped na raiz; assumir que Range se executa apenas por existir; tratar IOptionsSnapshot como singleton; confundir uma notificação com a atualização automática de cópias próprias.

Tópicos relacionados: Alojamento, middleware e percurso do pedido · Persistência e concorrência · Saúde, observabilidade e diagnóstico

Leva esta ideia contigo

Declara a unidade de vida e a fonte do valor. Depois decide quando ler, quando atualizar e quando rejeitar configuração inválida. Registo, binding e validação resolvem responsabilidades diferentes que precisam de estar ligadas explicitamente.

Criar conta

Referência: Dependency lifetime behavior · ASP.NET Core 10.0 / .NET 10 / C# 14