Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 42

.NET Aspire: Configuration and Options Pattern for Components

Recommended resources

Olá! Na lição anterior, mergulhamos no ciclo de diagnóstico do Aspire, aprendendo a combinar o Aspire Dashboard para observação e o depurador do IDE para uma análise de código detalhada. Vimos como a depuração "cross-service" funciona e como usar perfis de inicialização para controlar o ambiente de depuração.

Agora, vamos dar um passo fundamental para construir aplicações reais. O objetivo desta lição é entender como o .NET Aspire descobre e injeta configurações para dependências externas (componentes) usando o Padrão Options do ASP.NET Core. Este é o mecanismo central que torna a adição de bancos de dados, caches e filas de mensagens tão simplificada no Aspire.

1. O que é um Componente .NET Aspire?

Antes de entendermos o "como", vamos entender o "porquê". No desenvolvimento para a nuvem, simplesmente adicionar um pacote NuGet para um serviço como Redis ou PostgreSQL é apenas o começo. Você ainda precisa:

  • Configurar connection strings.
  • Implementar health checks para monitorar a saúde do serviço.
  • Configurar resiliência (ex: retries, circuit breakers).
  • Integrar logs, métricas e tracing (observabilidade).
  • Registrar o cliente do serviço no contêiner de Injeção de Dependência (DI) com o tempo de vida (lifetime) correto.

Fazer tudo isso manualmente para cada dependência é repetitivo e propenso a erros. Um Componente .NET Aspire é uma biblioteca "opinativa" que encapsula todas essas boas práticas.

Introducing .NET Aspire: Simplifying Cloud-Native ...

Para entender a filosofia por trás dos componentes, leia esta seção do blog introdutório do .NET Aspire. Ele contrasta a abordagem manual com a abordagem do Aspire e lista os requisitos essenciais que um componente deve cumprir.

Leia a seção "Deeper Dive" e o subtítulo "Components". Preste atenção especial na lista de tarefas que um desenvolvedor faria manualmente para configurar o Redis e como o Aspire simplifica isso. Note os quatro requisitos que um componente deve satisfazer.

O AppHost orquestra essas dependências, e os componentes cuidam da configuração dentro de cada serviço, como mostra o diagrama a seguir.

.NET Aspire AppHost Resource Diagram
Este diagrama ilustra como o projeto AppHost define os recursos da aplicação (como um cache em contêiner e projetos de serviço). O AppHost é responsável por fornecer a configuração necessária, como \"Connection Strings\", para que os serviços possam se comunicar com suas dependências.

O foco desta lição é o pilar da configuração detalhada e esquematizada (schematized configuration).


2. A Base: O Padrão Options no ASP.NET Core

O mecanismo de configuração do Aspire é construído sobre um padrão fundamental do ASP.NET Core: o Padrão Options. Como você tem uma vasta experiência em .NET, provavelmente já o encontrou. Vamos fazer uma revisão focada, pois ele é a chave para entender a "mágica" do Aspire.

O Padrão Options permite:

  1. Agrupar configurações relacionadas em classes C# fortemente tipadas.
  2. Vincular (bind) essas classes a seções de arquivos de configuração como appsettings.json.
  3. Registrar essas classes no contêiner de DI para que possam ser injetadas em outros serviços.

Isso promove a separação de preocupações e o encapsulamento, princípios de engenharia de software que você certamente valoriza.

Managing .NET App Configuration With The Options Pattern In ASP.NET Core

Este vídeo de Milan Jovanović oferece uma excelente e concisa explicação do Padrão Options. Ele cobre desde o básico até conceitos mais avançados como IConfigureOptions, que é crucial para o funcionamento dos componentes Aspire.

Assista ao vídeo do início até 08:36. Preste atenção especial aos seguintes pontos: Como usar builder.Services.Configure para vincular uma seção do JSON a uma classe C#. A diferença de comportamento e tempo de vida entre IOptions, IOptionsSnapshot e IOptionsMonitor. O uso da interface IConfigureOptions para encapsular a lógica de configuração em uma classe separada.

Para consolidar, a documentação da Microsoft nos fornece uma tabela de referência útil para as principais interfaces do padrão:

Options pattern in ASP.NET Core

A documentação oficial detalha as nuances de cada interface do Padrão Options. A tabela comparativa é especialmente útil para uma referência rápida.

Revise a seção "Options interfaces". Foque na lista de características para IOptions<T>, IOptionsSnapshot<T>, e IOptionsMonitor<T>. Entender a diferença de tempo de vida (Singleton vs. Scoped) e o suporte para recarga de configuração é fundamental.

Em resumo, o Padrão Options nos dá um framework robusto e extensível para gerenciar configurações em .NET. A interface IConfigureOptions é o ponto de extensão que os componentes Aspire utilizam para aplicar suas configurações automaticamente.

Teste seu entendimento!

Você precisa ler uma connection string em um serviço que é registrado como Singleton no contêiner de DI. Essa connection string pode ser atualizada em um sistema de gerenciamento de configuração (como Azure App Configuration) enquanto a aplicação está em execução, e seu serviço Singleton precisa usar o valor mais recente assim que ele mudar. Qual interface do Padrão Options você injetaria no seu serviço Singleton e por quê?

Show answer

Você injetaria IOptionsMonitor<T>.

  • Por quê? IOptionsMonitor<T> é registrado como um serviço Singleton, então ele pode ser injetado em outros Singletons. Mais importante, ele é projetado para detectar alterações na fonte de configuração e fornecer sempre o valor mais atual através da propriedade .CurrentValue. IOptions<T> também é Singleton, mas ele armazena em cache o valor da primeira vez e nunca o atualiza. IOptionsSnapshot<T> é Scoped, então não pode ser injetado diretamente em um serviço Singleton.

3. O "Como": O Fluxo de Injeção de Configuração do Aspire

Agora vamos conectar os pontos. Como o Aspire usa o Padrão Options para automatizar a configuração? O processo ocorre em quatro etapas:

Etapa 1: Definição do Recurso no AppHost
No Program.cs do seu projeto AppHost, você define um recurso e uma referência a ele:

var builder = DistributedApplication.CreateBuilder(args);

// 1. Um recurso 'cache' é definido. 
// Aspire sabe como iniciar um contêiner Redis e qual sua connection string.
var cache = builder.AddRedis("cache");

// 2. O projeto 'apiservice' é definido com uma referência ao recurso 'cache'.
// Isso sinaliza que 'apiservice' precisa da connection string do 'cache'.
var apiservice = builder.AddProject<Projects.MyAspireApp_ApiService>("apiservice")
                        .WithReference(cache);

builder.Build().Run();

Etapa 2: Injeção de Configuração pelo Orquestrador
Quando você executa o AppHost, o orquestrador do Aspire inicia o processo do apiservice. Ao fazer isso, ele injeta a connection string do Redis como uma variável de ambiente no processo do apiservice. O nome dessa variável segue uma convenção que o sistema de configuração do .NET entende:
ConnectionStrings__cache=localhost:6379

O duplo underscore __ é o delimitador de hierarquia do .NET para configuração. Isso é equivalente a ter o seguinte no appsettings.json:

{
  "ConnectionStrings": {
    "cache": "localhost:6379"
  }
}

Etapa 3: O Componente Aspire no Serviço
No projeto apiservice, você adiciona o pacote NuGet do componente, por exemplo, Aspire.StackExchange.Redis, e chama seu método de extensão no Program.cs:

// Dentro do Program.cs do 'apiservice'
var builder = WebApplication.CreateBuilder(args);

// 3. Este método de extensão contém a "mágica"
builder.AddRedis("cache"); 
// ...

Este método AddRedis("cache") faz o seguinte:

  • Ele usa o Padrão Options para configurar as opções do cliente Redis (RedisSettings).
  • Ele sabe que deve procurar a configuração na seção ConnectionStrings:cache (que foi injetada pela variável de ambiente).
  • Ele usa essa configuração para registrar o cliente Redis (IConnectionMultiplexer) no contêiner de DI com o tempo de vida correto e com resiliência e telemetria já configuradas.

Etapa 4: Uso Transparente no Código da Aplicação
Finalmente, seu código no apiservice se torna trivial. Você não precisa se preocupar com IConfiguration ou connection strings. Você simplesmente injeta o cliente pronto para uso:

// Em um service ou controller do 'apiservice'
public class MyRedisService(IConnectionMultiplexer redis)
{
    private readonly IDatabase _db = redis.GetDatabase();

    public async Task SetValueAsync(string key, string value)
    . . . 
}

O contrato que permite a um recurso do AppHost fornecer uma connection string é a interface IResourceWithConnectionString.

Deep Dive: Extending and Customizing Aspire

Para ver a perspectiva do AppHost, este trecho de um vídeo da equipe do .NET explica o primitivo IResourceWithConnectionString. Ele mostra como um recurso declara sua capacidade de fornecer uma connection string que outros podem consumir.

Assista ao trecho de 12:10 a 13:04. A discussão sobre IResourceWithConnectionString é o cerne de como o Aspire projeta essa capacidade de configuração no nível do AppHost.


4. A Descoberta: Configuração Esquematizada (Schematized)

Essa automação é poderosa, mas e se você precisar sobrescrever um comportamento padrão? Por exemplo, alterar o tempo limite de conexão do Redis?

É aqui que outra responsabilidade de um componente Aspire se torna crucial: fornecer um schema JSON para sua configuração.

Quando um componente fornece um schema, seu IDE (como Visual Studio ou Rider) pode oferecer IntelliSense (autocompletar) diretamente no seu arquivo appsettings.json.

Por exemplo, se você quisesse sobrescrever a configuração do Redis no apiservice, você poderia adicionar ao seu appsettings.Development.json:

{
  "Aspire": {
    "StackExchange": {
      "Redis": {
        // Ao digitar aqui, o IDE sugeriria propriedades como:
        "HealthChecks": false,
        "Tracing": true
        // E outras opções específicas do cliente Redis...
      }
    }
  }
}

Isso torna a configuração descobrível, eliminando a necessidade de vasculhar a documentação para encontrar os nomes exatos das chaves de configuração.


Conclusão

Nesta lição, desvendamos um dos mecanismos mais poderosos do .NET Aspire: a configuração automática de componentes.

Principais Pontos:

  • Componentes Aspire são pacotes opinativos que automatizam a configuração de resiliência, telemetria, health checks e injeção de dependência.
  • O mecanismo é construído sobre o Padrão Options do ASP.NET Core, que usa classes C# fortemente tipadas para representar configurações.
  • O fluxo é um ciclo completo:
    1. O AppHost define um recurso (ex: Redis) e uma referência a ele em um serviço.
    2. O Orquestrador Aspire injeta a configuração (ex: connection string) como uma variável de ambiente no processo do serviço.
    3. O pacote do componente no serviço usa IConfigureOptions para ler essa configuração e registrar o cliente no contêiner de DI.
    4. O código da aplicação simplesmente injeta e usa o cliente, sem conhecimento direto da configuração.
  • Os componentes fornecem schemas JSON, tornando as opções de configuração substituíveis e descobríveis diretamente no seu IDE através do appsettings.json.

Agora que você entende a teoria por trás de como o Aspire gerencia a configuração de um componente, a próxima lição será totalmente prática. Vamos integrar os três principais tipos de dependências em nossa aplicação: Redis para caching, PostgreSQL como nosso banco de dados e RabbitMQ para comunicação assíncrona.