.NET - Iniciando com o .NET Aspire - III
Neste artigo vamos por a mão na massa e usar alguns dos recursos do .NET Aspire. |
Continuando o artigo anterior vamos agora criar um projeto usando o template .NET Aspire Empty App.
Criando seu primeiro projeto .NET Aspire Empty App
O template .Net Aspire Empty App no .NET é um projeto básico de aplicação web. Ele gera uma estrutura mínima, permitindo ao desenvolvedor começar do zero sem qualquer configuração ou bibliotecas pré-adicionadas. Esse tipo de template é usado principalmente quando o desenvolvedor deseja controle total sobre as dependências e o fluxo de desenvolvimento, evitando configurações ou códigos que venham pré-configurados em outros templates mais complexos.
Aqui estão algumas características deste projeto:
Estrutura Mínima: Cria apenas o esqueleto da aplicação sem quaisquer middlewares, controladores ou modelos pré-configurados.
Alta Flexibilidade: O desenvolvedor precisa adicionar manualmente todos os recursos que deseja, como controladores, páginas, rotas e serviços.
Sem Front-end ou Back-end Pré-definido: Não inclui frameworks de front-end (como Razor Pages ou Angular) ou APIs.
Ideal para Customizações Específicas: É útil quando se deseja construir algo muito específico, sem precisar remover código desnecessário ou se adequar a padrões pré-definidos.
Assim vamos agora iniciar a criação de um projeto usando este template.
Abra o Visual Studio 2022 e localize o template .NET Aspire Empty App :
Informe o nome da solução - AspireApp - e escolha o local onde vai salvar o projeto:
A seguir selecione o target framework , .NET 8.0 , e clique em Create.
Com isso teremos na janela Solution Explorer uma solução contendo 2 projetos:
O template .NET Aspire
Empty App
oferece uma base sólida para construir aplicações
distribuídas, e a criação de dois projetos iniciais (.AppHost
e
.ServiceDefaults
)
é uma escolha intencional com objetivos específicos.
Compreendendo a estrutura da Solution:
Projeto AppHost: Atua como orquestrador central, o AppHost coordena a execução de todos os projetos dentro do aplicativo .NET Aspire. Ele gerencia dependências, define definições de configuração, gerencia o ciclo de vida da aplicação, iniciando e parando os serviços, e garante integração perfeita entre diferentes componentes.
Pode ser comparado ao
Program.cs
em um aplicativo ASP.NET Core tradicional, mas
com funcionalidades adicionais para gerenciar aplicações distribuídas.
Projeto ServiceDefaults: Fornece uma abordagem padronizada para configurar serviços, permitindo escalabilidade e manutenção mais fáceis do aplicativo. Contém configurações padrão para os serviços que serão utilizados na sua aplicação.
Este projeto permite que você defina configurações globais para serviços como bancos de dados, filas de mensagens, caches, etc., que podem ser sobreescritas em projetos específicos. Ao criar novos serviços, você pode herdar as configurações padrão deste projeto, evitando a duplicação de código.
A separação em dois projetos traz os seguintes benefícios:
Organização: Separa a lógica de configuração da aplicação da lógica dos serviços, facilitando a manutenção e a compreensão do código.
Reusabilidade: As configurações padrão definidas em
.
ServiceDefaults
podem ser reutilizadas em outros projetos, promovendo a consistência e
agilizando o desenvolvimento.
Extensibilidade: Permite que você adicione novos serviços à sua aplicação de forma modular, sem afetar a configuração principal da aplicação.
Após criar o projeto, você pode começar a construir sua aplicação seguindo estes passos:
Adicionar novos serviços: Crie novos projetos para seus serviços, como APIs, interfaces de usuário, workers de background, etc.
Configurar os serviços: Configure os serviços em cada projeto, sobrescrevendo as configurações padrão se necessário.
Registrar os serviços no AppHost: Registre os serviços no
projeto .AppHost
para que eles sejam
gerenciados pelo sistema.
Definir endpoints: Defina os endpoints HTTP que serão expostos por cada serviço.
Implementar a lógica de negócio: Implemente a lógica de negócio de cada serviço.
Para ilustrar vamos criar um projeto ASP.NET Core Web API que vai atuar como serviço de back-end e a seguir criar uma aplicação Blazor que vai atuar como Front-énd.
Criando o projeto ASP.NET Core Web API
Vamos incluir um novo projeto na solução criada usando a opção Add-> New Project;
Selecione o template - ASP.NET Core Web API e clique em Next;
Informe o nome ApireApp.API e clique em Next;
Defina as configurações conforme a imagem abaixo e clique em Create:
Observe a opção - Enlist in .NET Aspire orchestration, essa opção conecta o novo projeto Web API aos serviços e infraestrutura de orquestração fornecidos pelo projeto Aspire. A orquestração, no contexto .NET Aspire, refere-se ao gerenciamento centralizado de comunicação entre os diferentes serviços da solução, incluindo autenticação, autorização, roteamento, logs, telemetria e outras funcionalidades comuns a todos os serviços.
Assim, o projeto Web API que criado será registrado automaticamente no projeto AppHost, que gerencia a inicialização e a configuração dos serviços Aspire.
O novo projeto Web API irá herdar os mecanismos centralizados de autenticação e autorização definidos na infraestrutura do .NET Aspire, permitindo uma segurança padronizada. Serviços como logs, telemetria, rastreamento de erros e métricas de performance estarão centralizados no AppHost.
O projeto Web API também terá acesso às configurações centralizadas, como bancos de dados, serviços externos e outros recursos necessários para todos os serviços no ambiente Aspire.
Caso você decida não usar a opção "Enlist in .NET Aspire Orchestration", seu projeto Web API será criado de forma isolada, sem se integrar à estrutura de orquestração Aspire, e, isso trará as seguintes consequências:
Você terá que gerenciar manualmente as configurações que o Aspire oferece automaticamente. Isso inclui a configuração de serviços de autenticação, autorização, logs, telemetria e outras funcionalidades compartilhadas.
Seu projeto Web API não será registrado no AppHost, então qualquer benefício de orquestração centralizada, como gestão de lifecycle, registro de serviços ou integração com outros projetos na solução, terá que ser implementado separadamente.
Você terá que implementar mecanismos de segurança no próprio projeto, o que pode causar inconsistências em relação aos outros serviços Aspire.
À medida que sua solução cresce e novos serviços são adicionados, a manutenção de cada projeto Web API isolado pode se tornar mais complexa, já que cada um terá suas próprias configurações e não compartilhará os recursos comuns da orquestração Aspire.
Resumindo, ao não usar a opção "Enlist in .NET Aspire Orchestration", você perde a integração automática com a infraestrutura centralizada do Aspire, o que implica na necessidade de gerenciar manualmente várias funcionalidades que, de outra forma, seriam oferecidas automaticamente.
Em nosso exemplo vamos marcar esta opção e com isso teremos o projeto incluido em nossa Solution conforme mostra a imagem a seguir:
Vamos remover o controlador criado por padrão e a classe WeatherForecast do projeto.
Agora vamos criar o controlador TarefasController na pasta Controllers e definir 3 operações para incluir, alterar e obter dados de tarefas usando os métodos Get , Post e Delete :
using Microsoft.AspNetCore.Mvc; namespace AspireApp.API.Controllers; [Route("api/[controller]")] [ApiController] public class TarefasController : ControllerBase { private static readonly List<string> tarefas = new List<string>(); // GET: api/tarefa [HttpGet] public ActionResult<IEnumerable<string>> Get() { return Ok(tarefas); } // POST: api/tarefa [HttpPost] public ActionResult Post([FromBody] string tarefa) { if (string.IsNullOrWhiteSpace(tarefa)) { return BadRequest("Tarefa não pode estar vazia"); } tarefas.Add(tarefa); return Ok(); } // DELETE: api/tarefa/{index} [HttpDelete("{index}")] public ActionResult Delete(int index) { if (index < 0 || index >= tarefas.Count) { return NotFound("Tarefa não encontrada"); } tarefas.RemoveAt(index); return Ok(); } } |
Com isso definimos 3 endpoints em nossa API para realizar as operações básicas. Observe que não estamos usando um banco de dados para simplificar o projeto.
Na classe Program temos a configuração dos serviços onde já temos configurado o serviço do Swagger:
var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.MapDefaultEndpoints(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); |
Criando a aplicação Blazor
Para realizar a integração de um Front-end em nossa aplicação .NET Aspire vamos incluir um novo projeto Blazor em nossa solução.
Usando a opção Add-> New Project, selecione o template Blazor Web App e clique em Next;
Informe o nome AspireApp.BlazorTarefas e clique em Next definindo as seguinte configurações:
Clicando no botão Create teremos o projeto incluido em nossa solução:
Neste projeto vamos remover os componentes WeatherForecast.razor e Counter.razor da pasta Components/Pages. A seguir vamos alterar o código do componente NavMenuRazor conforme abaixo:
<div class="top-row ps-3 navbar navbar-dark"> <div class="container-fluid"> <a class="navbar-brand" href="">AspireApp.BlazorTarefas</a> </div> </div> <input type="checkbox" title="Navigation menu" class="navbar-toggler" /> <div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()"> <nav class="flex-column"> <div class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="tarefas"> <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Tarefas </NavLink> </div> </nav> </div> |
Com isso criamos a opção Tarefas no menu mapeando para a rota tarefas.
Agora vamos criar uma pasta Services e nesta pasta criar a classe TarefaService que vai interagir com os endpoints da nossa API e gerencias as tarefas para incluir, retornar e excluir dados.
namespace AspireApp.BlazorTarefas.Services; public class TarefaService { private readonly HttpClient _httpClient; public TarefaService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<List<string>> GetTarefasAsync() { var tarefas = await _httpClient.GetFromJsonAsync<List<string>>("api/tarefas"); return tarefas ?? new List<string>(); } public async Task AddTarefaAsync(string tarefa) { if (tarefa is null) throw new Exception("Dados inválidos"); var response = await _httpClient.PostAsJsonAsync("api/tarefas", tarefa); if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"Falha ao adicionar a tarefa. Status code: {response.StatusCode}"); } } public async Task DeleteTarefaAsync(int index) { var response = await _httpClient.DeleteAsync($"api/tarefas/{index}"); if (!response.IsSuccessStatusCode) { throw new HttpRequestException($"Falha ao deletar a tarefa. Status code: {response.StatusCode}"); } } } |
Vamos definir agora a interface com o usuário criando o componente Tarefa.razor na pasta Components/Pages :
@page "/tarefas"
@rendermode InteractiveServer
<h3>Lista de Tarefas</h3> <div class="input-group mb-3"> <input @bind="novaTarefa" class="form-control" placeholder="Nova tarefa" /> <button @onclick="AddTarefa" class="btn btn-primary">Incluir</button> </div> <ul class="list-group"> @foreach (var tarefa in tarefas) { <li class="list-group-item d-flex justify-content-between align-items-center"> @tarefa <button @onclick="() => DeleteTarefa(tarefa)" class="btn btn-danger btn-sm">Excluir</button> </li> } </ul> @if (tarefas.Count == 0) { <p>Sem tarefas ainda.</p> } @code { private List<string> tarefas = new List<string>(); private string? novaTarefa; protected override async Task OnInitializedAsync() { await LoadTarefas(); } private async Task LoadTarefas() { tarefas = await TarefaService.GetTarefasAsync(); } private async Task AddTarefa() { if (!string.IsNullOrWhiteSpace(novaTarefa)) { await TarefaService.AddTarefaAsync(novaTarefa); await LoadTarefas(); novaTarefa = string.Empty; } } private async Task DeleteTarefa(string tarefa) { var index = tarefas.IndexOf(tarefa); if (index != -1) { await TarefaService.DeleteTarefaAsync(index); await LoadTarefas(); } } } |
Na classe Program do projeto vamos configurar um cliente HttpClient com um BaseAddress para poder realizar a comunicação com a API:
builder.Services.AddHttpClient<TarefaService>(client => client.BaseAddress = new("http://apiservice"));} |
Podemos usar uma outra abordagem para definir o BaseAddress usando o código abaixo:
builder.Services.AddHttpClient<TarefaService>(client => { var url = builder.Configuration["TarefaEndpoint"] ?? throw new InvalidOperationException("Endpoints não foram definidos !!!"); client.BaseAddress = new(url); }); |
E a seguir definir os endpoints no arquivo appsettings.json:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "TarefaEndpoint": "http://apiservice", "TarefaEndpointHttps" : "https://apiservice" } |
Para realizar a conexão do projeto Blazor com o projeto API na solução .NET Aspire vamos alterar o código da classe Program no projeto ApireApp.AppHost incluindo os dois projetos com o seguinte código:
var builder = DistributedApplication.CreateBuilder(args); var apiservice = builder.AddProject<Projects.AspireApp_API>("apiservice"); builder.AddProject<Projects.AspireApp_BlazorTarefas>("webfrontend") .WithReference(apiservice); builder.Build().Run(); |
Esse trecho de código configura um aplicativo .NET Aspire que consiste em dois projetos interconectados. Ele configura um aplicativo .NET Aspire modular, onde o frontend Blazor pode consumir os serviços do backend API.
Um serviço API: Chamado de "apiservice", este projeto provavelmente contém a lógica de negócios e o acesso aos dados do seu aplicativo.
Um frontend Blazor: Denominado "webfrontend", este projeto é responsável pela interface do usuário, permitindo que os usuários interajam com o serviço API.
Essa arquitetura permite uma melhor separação de responsabilidades e facilita a manutenção e o desenvolvimento do aplicativo.
Vamos executar o projeto localmente pressionando F5 (o projeto AppHost tem que estar definido como o Startup). Como resultado teremos a exibição do dashboard abaixo:
Podemos ver os projetos da aplicação Blazor(front-end) e Web API(back-end) no Dashboard. A partir deste dashboard, podemos monitorar logs, métricas e rastreamento e facilitar a comunicação entre os dois apps sem precisar de referências diretas ou URLs específicas.
Clicando no link do projeto apiservice teremos os endpoints da API:
Clicando no link do projeto webfrontend teremos a aplicação Blazor:
E podemos consumir os endpoints da API na aplicação Blazor:
Pegue o projeto aqui : AspireApp.zip
E estamos conversados...
"Há um caminho que ao homem parece direito, mas o fim dele são os caminhos da
morte."
Provérbios 14:12
Referências:
NET - Unit of Work - Padrão Unidade de ...