Olá! Bem-vindo de volta ao nosso curso sobre .NET Aspire.
Na lição anterior, você aprendeu a construir uma comunicação robusta entre serviços usando o padrão Typed HTTP Client, combinado com a descoberta de serviços do Aspire. Vimos como registrar um cliente e usar um nome de serviço lógico (ex: http://products-api) para que o Aspire resolva o endereço real em tempo de execução.
Até agora, assumimos um cenário simples: um serviço expõe um ponto de acesso principal. Mas e se um serviço precisar expor várias portas para diferentes finalidades? Por exemplo:
- Uma porta para a API pública (HTTPS).
- Uma porta diferente para um painel de administração interno (HTTP).
- Uma terceira porta para comunicação via gRPC.
Como podemos endereçar esses pontos de acesso específicos sem voltar a hard-codificar portas em nosso código? A lição de hoje responde exatamente a essa pergunta. Nosso objetivo de aprendizagem é usar endpoints nomeados (named endpoints) para expor e consumir múltiplas portas de um único serviço.
O Conceito de Endpoints Nomeados
A ideia por trás dos endpoints nomeados é simples, mas poderosa. Além do endpoint padrão (geralmente http ou httpss) que o Aspire configura para um serviço, você pode definir endpoints adicionais, cada um com um nome exclusivo.
Isso permite que outros serviços descubram e se comuniquem não apenas com o serviço em si, mas com uma porta específica daquele serviço, usando uma convenção de nomenclatura.
1. Definindo Múltiplos Endpoints no AppHost
A configuração de múltiplos endpoints é feita no seu projeto AppHost. Enquanto WithHttpEndpoint() e WithHttpsEndpoint() são ótimos para os casos padrão, o método WithEndpoint() oferece a flexibilidade de que precisamos para criar endpoints nomeados.
Para entender os detalhes de como o WithEndpoint() funciona, a documentação oficial do Aspire é o melhor recurso.
Inner-loop networking overview - Aspire
Este artigo da documentação do .NET Aspire, intitulado 'Inner-loop networking overview', detalha como o Aspire gerencia a rede em seu ambiente de desenvolvimento local. Vamos nos concentrar na seção que explica como criar endpoints personalizados.
Leia a seção intitulada 'Endpoint extension methods'. Preste atenção especial ao exemplo que usa uma 'callback delegate' para configurar um endpoint nomeado 'admin'. Observe como as propriedades endpointName, Port, UriScheme e Transport são utilizadas para definir o novo ponto de acesso.
Como você leu, podemos adicionar múltiplos endpoints a um serviço de forma fluente. Vamos a um exemplo prático. Suponha que temos um serviço ApiService que precisa expor sua API principal via HTTPS e um painel de métricas interno via HTTP na porta 9000.
No Program.cs do seu AppHost, a configuração seria assim:
// No projeto AppHost
var apiService = builder.AddProject<Projects.ApiService>("apiservice")
// 1. Define o endpoint padrão (HTTPS) que já conhecemos.
.WithHttpsEndpoint()
// 2. Define um novo endpoint nomeado 'metrics' para o painel.
.WithEndpoint(
endpointName: "metrics",
port: 9000,
scheme: "http");
// Outro serviço que precisa chamar o apiService.
builder.AddProject<Projects.Frontend>("frontend")
.WithReference(apiService);
Com essa configuração, o apiService agora possui dois pontos de acesso conhecidos pelo Aspire: o endpoint HTTPS padrão e um endpoint HTTP chamado metrics na porta 9000.
2. Consumindo Endpoints Nomeados
Agora que definimos nosso endpoint nomeado, como o serviço frontend pode consumi-lo? A descoberta de serviços do Aspire utiliza uma convenção de URI específica para resolver endpoints nomeados:
scheme://_endpointName.serviceName
Note o caractere _ (underscore) antes do nome do endpoint. Este é o "sentinela" que sinaliza ao mecanismo de descoberta de serviços do Aspire que você está solicitando um endpoint nomeado, e não o padrão.
A documentação sobre Service Discovery explica isso de forma concisa.
Vamos consultar a documentação oficial 'Service discovery' para ver exatamente como consumir o endpoint que acabamos de criar.
Leia as seções 'Named endpoints' e 'Named endpoints in Aspire'. O foco aqui é entender a sintaxe _endpointName.serviceName e ver como ela é aplicada tanto na configuração do HttpClient quanto no código do AppHost.
Com base no que você leu, podemos configurar dois HttpClients diferentes no Program.cs do nosso serviço frontend: um para a API principal e outro para o painel de métricas.
// No Program.cs do projeto Frontend
// Cliente para a API principal (endpoint padrão)
builder.Services.AddHttpClient<ApiServiceApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://apiservice");
});
// Cliente para o painel de métricas (endpoint nomeado 'metrics')
builder.Services.AddHttpClient<ApiServiceMetricsClient>(client =>
{
// A convenção para endereçar o endpoint nomeado
client.BaseAddress = new Uri("http://_metrics.apiservice");
});
E é isso! Você agora tem dois Typed Clients distintos, cada um apontando para uma porta diferente do mesmo serviço, tudo sem hard-codificar nenhuma URL ou porta. O Aspire gerencia toda a resolução de endereços para você.
Quando você executa sua aplicação Aspire, o Dashboard refletirá esses múltiplos endpoints.

Teste seu entendimento!
Imagine que você está expandindo um serviço catalog-api. Além da sua API REST (o endpoint padrão httpss), você precisa adicionar um endpoint gRPC nomeado grpc na porta 8081.
1. No AppHost, como você modificaria a definição do serviço catalog-api?
// Início do código
var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
.WithHttpsEndpoint(); // Endpoint padrão existente
// Qual linha de código você adicionaria aqui?
2. Em um serviço consumidor, como você registraria um HttpClient (ou, mais realisticamente, um GrpcChannel) para se comunicar com este novo endpoint gRPC?
// Início do código
builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
// Qual URI você usaria aqui?
o.Address = new Uri("...coloque a URI aqui...");
});
Show answer
Resposta 1 (AppHost):
Você adicionaria uma chamada WithEndpoint especificando o nome e a porta. Para gRPC, o scheme também é http.
var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
.WithHttpsEndpoint()
// Linha adicionada
.WithEndpoint(endpointName: "grpc", port: 8081, scheme: "http");
Resposta 2 (Consumidor):
Você usaria a convenção _endpointName.serviceName.
builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("http://_grpc.catalog-api");
});
Conclusão
Nesta lição, você adicionou uma ferramenta poderosa ao seu arsenal do .NET Aspire. Agora você sabe como lidar com serviços que possuem múltiplos pontos de acesso de uma maneira limpa e dinâmica.
Principais Pontos:
- Serviços podem expor mais de um endpoint para diferentes finalidades (ex: REST, gRPC, admin UI).
- Use o método
WithEndpoint(endpointName: "...")no AppHost para definir endpoints nomeados com portas e esquemas específicos. - Para consumir um endpoint nomeado, use a convenção de URI
scheme://_endpointName.serviceName. - O Aspire gerencia a resolução desses endereços, mantendo seu código de serviço limpo de configurações de infraestrutura.
Essa flexibilidade é crucial na construção de sistemas distribuídos do mundo real, onde um único microsserviço pode precisar se comunicar por diferentes protocolos ou expor interfaces distintas.
Próxima lição:
Até agora, configuramos a comunicação, tanto para endpoints padrão quanto nomeados. Mas como podemos ter certeza de que tudo está funcionando como esperado? Na próxima lição, vamos mergulhar no Aspire Dashboard para verificar a comunicação serviço-a-serviço, inspecionando traces distribuídos para diagnosticar latência e erros.