![]() |
Neste artigo veremos como usar os recursos do Carter para refatoração nas minimal APIs da ASP.NET Core. |
Se você ainda não conhece o Carter veja o meu artigo neste link : ASP .NET Core - Criando APIs com o framework Carter.
O Carter é um framework minimalista para o desenvolvimento de APIs web em C#. Ele foi inspirado pelo Sinatra, um framework similar em Ruby, e é projetado para ser simples, direto e fácil de usar.
Aqui estão alguns dos principais recursos e conceitos do Carter:
- O Carter permite definir rotas de forma simples e direta, especificando métodos HTTP e padrões de URL.
- Você pode modelar as requisições que sua API aceita e as respostas que ela retorna usando classes simples em C#.
- Assim como outros frameworks, Carter suporta middlewares, que são componentes que podem processar uma requisição antes que ela chegue ao seu endpoint.
- É possível usar serviços e componentes através de injeção de dependência, facilitando a organização e teste de código.
- Carter é altamente extensível, permitindo que você adicione seus próprios manipuladores, middlewares e plugins para estender suas capacidades.
- Com o plugin Carter.OpenApi, é possível gerar automaticamente a documentação da API usando o padrão OpenAPI (anteriormente conhecido como Swagger).
Criando uma minimal API
Para mostrar seus recursos na prática vamos criar um novo projeto no VS 2022 usando o template ASP.NET Core Web API com o nome MinApiCarter e vamos selecionar a opção para não usar Controllers e criar uma minimal API.
Vamos criar a pasta Entities no projeto e nesta pasta vamos criar a entidade Product:
public class Product { public int ProductId { get; set; } public string? ProductName { get; set; } public decimal Price { get; set; } public DateTime CreatedOn { get; set; } public bool IsValid() { return ProductId != 0 && !string.IsNullOrWhiteSpace(ProductName) && Price >= 0; } } |
Nossa entidade possui 4 propriedades e o método IsValid
que verifique se ProductId é deferente de zero, se o
ProductName não está
vazio ou nulo e se o Price
é maior ou igual a zero. Este método retorna true se todas as
condições forem atendidas e false caso contrário.
A seguir vamos criar a pasta Repositories onde vamos criar a interface IProductRepository e a classe concreta ProductRepository:
1- IProductRepository
public interface IProductRepository { bool CreateProduct(Product product); IEnumerable<Product> GetProducts(); Product GetProductById(int id); bool UpdateProduct(int id, Product product); bool DeleteProduct(int id); } |
2- ProductRepository
using Bogus; using MinApiCarter.Entities; namespace MinApiCarter.Repositories; public class ProductRepository : IProductRepository { private List<Product>? products; private readonly ILogger<IProductRepository> _logger; public ProductRepository(ILogger<IProductRepository> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); GenerateSeedData(); } public bool CreateProduct(Product product) { var productNameExistsAlready = products.Any(p => p.ProductName == product.ProductName); var produtoIdExistsAlready = products.Any(p =>
p.ProductId == product.ProductId);
if (productNameExistsAlready || produtoIdExistsAlready) { _logger.LogError("Product exists already!"); return false; } products.Add(product); return true; } public IEnumerable<Product> GetProducts() { return products; } public Product GetProductById(int id) { return products.FirstOrDefault(a => a.ProductId == id); } public bool UpdateProduct(int id, Product product) { var p = products.FirstOrDefault(a => a.ProductId == id); p.ProductName = product.ProductName; p.CreatedOn = DateTime.Now; p.Price = product.Price; return true; } public bool DeleteProduct(int id) { var product = products.FirstOrDefault(p => p.ProductId == id); if (product == null) { return false; } products.Remove(product); return true; } private void GenerateSeedData() { products = new Faker<Product>() .RuleFor(p => p.ProductId, p => p.IndexFaker) .RuleFor(p => p.ProductName, p => p.Commerce.ProductName()) .RuleFor(p => p.Price, p => Convert.ToDecimal(p.Finance.Amount())) .RuleFor(p => p.CreatedOn, p => p.Date.Recent()) .Generate(50); } } |
ENeste código estamos implementando a interface IProductRepository e estamos usando o pacote nuget Bogus para criar gerar dados fictícios para os produtos. Vamos gerar 50 produtos.
Definindo os endpoints
Na classe Program precisamo registrar o serviço do repositório e
criar os endpoints para realizar as operações :
using Carter; using MinApiCarter.Entities; using MinApiCarter.Repositories; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddSingleton<IProductRepository, ProductRepository>(); builder.Services.AddCarter(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } //app.MapCarter(); app.UseHttpsRedirection(); // PRODUCTS operations app.MapPost("/product/create", (Product product, IProductRepository productsRepo) => { if (!product.IsValid()) return Results.BadRequest(); productsRepo.CreateProduct(product); return Results.StatusCode(201); }); app.MapGet("/product/", (IProductRepository repo) => { return Results.Ok(repo.GetProducts()); }); app.MapGet("/product/{id}", (int id, IProductRepository repo) => { if (id <= 0) return Results.BadRequest(); return Results.Ok(repo.GetProductById(id)); }); app.MapPut("/product/update/{id}", (int id, Product product, IProductRepository productRepo) => { if (!product.IsValid()) return Results.BadRequest(); productRepo.UpdateProduct(id, product); return Results.Ok(); }); app.MapDelete("/product/delete/{id}", (int id, IProductRepository productsRepo) => { return productsRepo.DeleteProduct(id); }); app.Run(); |

Note que mesmo com poucos endpoints a classe Program já apresenta muito código e em projetos mais complexos o problema tende a se agravar gerando um código difícil de ler e de menter.
Podemos refatorar o código separando os endpoints em uma classe a parte e podemos usar os recursos do Carter para fazer isso.
Refatorando os endpoints com Carter
Vamos incluir no projeto o pacote Carter e a seguir registrar o serviço na classe Program:
builder.Services.AddCarter();
Agora vamos criar uma pasta Modules no projeto e nesta classe vamos criar a classe ProductsModule onde vamos implementar a interface ICarterModule e refatorar os endpoints da API conforme o código abaixo:
public class ProductsModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapPost("/products/create", CreateProduct); app.MapGet("/products/", GetAllProducts); app.MapGet("/products/{id}", GetProductById); app.MapPut("/products/update/{id}", UpdateProduct); app.MapDelete("/products/delete/{id}", DeleteProduct); } private IResult GetAllProducts(IProductRepository productsRepo) { var products = productsRepo.GetProducts(); return Results.Ok(products); } private IResult CreateProduct(Product product, IProductRepository productsRepo) { if (!product.IsValid()) return Results.BadRequest(); productsRepo.CreateProduct(product); return Results.StatusCode(201); } private IResult GetProductById(int id, IProductRepository repo) { if (id <= 0) return Results.BadRequest(); return Results.Ok(repo.GetProductById(id)); } private IResult UpdateProduct(int id, Product product, IProductRepository productRepo) { if (!product.IsValid()) return Results.BadRequest(); productRepo.UpdateProduct(id, product); return Results.Ok(); } private bool DeleteProduct(int id, IProductRepository repo) { return repo.DeleteProduct(id); } } |
Observe que todos os endpoints retornam IResult, exceto o
método DeleteProduct, que retorna um booleano. Você não precisa
agrupar o resultado do endpoint sempre no objeto Results. Cabe a você retornar o
que quiser de um endpoint.
Agora, nossos endpoints no método
AddRoutes parecem limpos depois que movemos nossas implementações de
endpoint para métodos privados na classe ProductsModule.
Agora podemos ajustar o código da classe Program :
using Carter; using MinApiCarter.Repositories; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddSingleton<IProductRepository, ProductRepository>(); builder.Services.AddCarter(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapCarter(); app.Run(); |
Com isso refatoramos os endpoints da nossa minimal API usando o Carter, movendo-os para um módulo separado.
E estamos conversados...![]()
Pegue o projeto aqui :
MinApiCarter.zip
"Olhai para as aves do céu, que nem semeiam, nem
segam, nem ajuntam em celeiros; e vosso Pai celestial as alimenta. Não tendes
vós muito mais valor do que elas?"
Mateus 6:26
Referências: