As APIs mínimas são um recurso empolgante introduzido no .NET 6, projetado para revolucionar a maneira como você cria APIs.
Imagine construir APIs robustas com código mínimo e zero boilerplate—não mais lutar com controladores, roteamento ou middleware. Isso é o que as APIs mínimas permitem que você faça. A ideia com essas APIs é simplificar o processo de desenvolvimento, tornando-o incrivelmente fácil e eficiente.
Neste artigo, mergulharemos no mundo das APIs mínimas no .NET 8 e guiaremos você na criação de uma API de livraria completamente funcional. Você aprenderá como obter todos os livros, recuperar um livro por seu ID, adicionar novos livros e até mesmo excluir livros. Vamos começar.
Sumário
Pré-requisitos
Antes de começarmos, certifique-se de que os seguintes pré-requisitos estão instalados na sua máquina:
-
Visual Studio Code ou qualquer outro editor de código de sua escolha
-
C# Dev Kit para Visual Studio Code
Alternatively, you can use Visual Studio 2022, which comes with built-in support for .NET 8. But in this article, we’ll be using Visual Studio Code. It’s lightweight, easy to use, and cross-platform.
Vamos usar o Swagger UI para testar nossa API. O Swagger UI é uma ferramenta poderosa que permite interagir com sua API diretamente do navegador. Ele oferece uma interface amigável para testar os endpoints da sua API, facilitando o teste e a depuração da mesma.
Quando você cria um novo projeto, ele instala automaticamente os pacotes necessários e configura o projeto para usar o Swagger UI. O .NET 8 inclui o Swagger UI por padrão, então, seja você criando sua aplicação no Visual Studio ou com .NET, o Swagger UI será configurado para você.
Execute sua aplicação e o Swagger UI abrirá automaticamente no seu navegador – mas, como estamos usando o VS Code, precisamos clicar no número da porta no nosso terminal.
Você pode encontrar o código-fonte para este projeto no GitHub.
Introdução às APIs Mínimas
Imagine trabalhar em um código-fonte com inúmeros endpoints, tornando-o bastante grande e complexo. Tradicionalmente, construir uma API no ASP.NET Core envolve o uso de controladores, roteamento, middleware e uma quantidade significativa de código boilerplate. Mas existem duas abordagens para construir uma API no ASP.NET Core: o jeito tradicional e o jeito minimalista.
O jeito tradicional é familiar para a maioria dos desenvolvedores, envolvendo controladores e um código de infraestrutura extenso. O jeito minimalista, introduzido no .NET 6
, permite que você crie APIs com código mínimo e zero boilerplate. Esta abordagem simplifica o processo de desenvolvimento, permitindo que você se concentre em escrever lógica de negócios em vez de lidar com código de infraestrutura.
As APIs mínimas são leves, rápidas e perfeitas para construir APIs de pequeno a médio porte. Elas são ideais para prototipagem, construção de microsserviços ou criação de APIs simples que não requerem muita complexidade. Neste manual, exploraremos o mundo das APIs mínimas no .NET 6 e aprenderemos como criar uma API de livraria completamente funcional do zero.
Como Criar uma API Mínima
Criar uma API mínima é straightforward ao usar o dotnet CLI
, pois o template padrão já é uma API mínima. Mas se você usar o Visual Studio, precisará remover o código boilerplate que vem com o template do projeto.
Vamos começar usando o dotnet CLI
para criar um projeto de API mínima.
dotnet new webapi -n BookStoreApi
O comando dotnet new webapi
cria um novo projeto de API mínima chamado BookStoreApi
. Este projeto contém os arquivos e pastas necessários para começar.
Vamos explorar a estrutura do projeto:
-
Program.cs
: O ponto de entrada da aplicação, onde o host é configurado. -
bookapi-minimal.sln
: O arquivo de solução que contém o projeto. -
bookapi-minimal.http
: Um arquivo que contém solicitações HTTP de exemplo para testar a API. -
bookapi-minimal.csproj
: O arquivo do projeto que contém a configuração do projeto. -
appsettings.json
: O arquivo de configuração que armazena as configurações da aplicação. -
appsettings.Development.json
: O arquivo de configuração para o ambiente de desenvolvimento.
Quando você abrir o arquivo program.cs, você vai notar que o código é mínimo. O arquivo Program.cs
contém o seguinte código:
var builder = WebApplication.CreateBuilder(args);
// Adicione serviços ao contêiner.
// Saiba mais sobre como configurar o Swagger/OpenAPI em https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure o pipeline de solicitação HTTP.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Se você ainda não entende completamente o código, não se preocupe – iremos abordá-lo em detalhes nas próximas seções. A ideia principal é que as APIs mínimas requerem muito pouco código, o que é uma de suas principais vantagens.
O código padrão configura uma API simples de previsão do tempo que você pode usar para testar sua configuração. Ele gera uma lista de previsões do tempo e as retorna quando você faz uma solicitação GET
para o endpoint /weatherforecast
. Além disso, o código inclui o Swagger UI para ajudá-lo a testar a API.
Preste atenção especial ao método app.MapGet
, que mapeia uma rota para uma função de manipulador. Neste caso, ele mapeia a rota /weatherforecast
para uma função que retorna uma lista de previsões do tempo. Vamos usar métodos semelhantes para criar nossos próprios endpoints nas próximas seções.
Antes de começarmos a criar a estrutura de pastas do nosso projeto, vamos entender os métodos HTTP em APIs baseadas em Controladores e APIs Mínimas.
Métodos HTTP em APIs baseadas em Controladores e APIs Mínimas
Em uma abordagem baseada em controladores, que é a maneira tradicional de criar APIs da web, você precisa criar uma classe de controlador e definir métodos para cada método HTTP. Por exemplo:
-
Para criar um método
GET
, você usa o atributo[HttpGet]
. -
Para criar um método
POST
, você usa o atributo[HttpPost]
. -
Para criar um método
PUT
, você usa o atributo[HttpPut]
. -
Para criar um método
DELETE
, você usa o atributo[HttpDelete]
.
Assim são criados os endpoints em uma abordagem baseada em Controlador.
Em contraste, as APIs Mínimas usam métodos como app.MapGet
, app.MapPost
, app.MapPut
e app.MapDelete
para criar endpoints. Esta é a principal diferença entre as duas abordagens: APIs baseadas em Controlador usam atributos para definir endpoints, enquanto APIs Mínimas usam métodos.
Agora que você entende como lidar com solicitações HTTP tanto em APIs baseadas em Controlador quanto em APIs Mínimas, vamos criar a estrutura de pastas do nosso projeto.
Antes de criarmos a estrutura de pastas do nosso projeto, vamos primeiro executar o que temos. Como aprendemos anteriormente, quando você cria um projeto com o Visual Studio ou o .NET CLI, ele vem com um projeto padrão WeatherForecast que podemos executar e ver na UI. Vamos executá-lo para garantir que tudo funcione antes de irmos criar a pasta do nosso projeto.
Execute este comando:
dotnet run
Você deve ver a seguinte saída:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5228
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\Devolopemnt\Dotnet\bookapi-minimal
Isso significa que a aplicação está em execução e ouvindo em http://localhost:5228
. Como mencionei acima, já que estamos usando o dotnet CLI
e o Visual Studio Code, a aplicação não abrirá automaticamente o navegador para nós. Precisamos fazer isso manualmente.
Abra seu navegador e navegue até http://localhost:5228/swagger/index.html
para ver a resposta padrão da API.
Você deve ver algo como isso:
Agora, a próxima coisa que precisamos fazer é encontrar uma maneira de estruturar nosso projeto e criar os arquivos e pastas necessários para começarmos.
Arquivos de Projeto de API Mínima
Para organizar nosso projeto, criaremos uma hierarquia de pastas estruturada. Isso ajudará a manter nosso código limpo e manutenível. Aqui está a estrutura de pastas que usaremos:
-
AppContext: Contém o contexto do banco de dados e as configurações relacionadas.
-
Configurações: Mantém as configurações do Entity Framework Core e dados de semente para o banco de dados.
-
Contratos: Contém Objetos de Transferência de Dados (DTOs) usados em nossa aplicação.
-
Endpoints: Onde definimos e configuramos nossos endpoints de API mínima.
-
Exceções: Contém classes de exceção personalizadas usadas no projeto.
-
Extensões: Mantém métodos de extensão que usaremos ao longo do projeto.
-
Modelos: Contém modelos de lógica de negócios.
-
Serviços: Contém classes de serviço que implementam a lógica de negócios.
-
Interfaces: Mantém definições de interface usadas para mapear nossos serviços.
No Visual Studio Code, você pode criar essa estrutura de pasta da seguinte forma:
- AppContext
- Configurations
- Contracts
- Endpoints
- Exceptions
- Extensions
- Models
- Services
- Interfaces
Depois de configurar, a estrutura de pasta do seu projeto deve ficar assim:
Agora que nossa estrutura de projeto está configurada, podemos prosseguir e começar a escrever nosso código. Vamos começar criando nossos modelos.
Como Criar os Modelos
Nesta seção, criaremos modelos para nossa aplicação. Modelos são os blocos de construção da nossa aplicação, representando os dados com os quais nossa aplicação trabalhará. Para nosso exemplo, criaremos um modelo para um livro.
Para começar, crie uma pasta chamada Models
no diretório do seu projeto. Dentro dessa pasta, crie um arquivo chamado BookModel.cs
e adicione o seguinte código:
// Models/BookModel.cs
namespace bookapi_minimal.Models
{
public class BookModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
Esta classe BookModel
define as propriedades que representam os detalhes de um livro, como seu título
, autor
, descrição
, categoria
, idioma
e número total de páginas
. Cada propriedade é projetada para armazenar informações específicas sobre o livro, facilitando a gestão e manipulação dos dados do livro dentro da nossa aplicação.
Agora que criamos nosso modelo, vamos criar nosso contexto de banco de dados.
Como Criar o Contexto de Banco de Dados
O contexto de banco de dados é uma classe que representa uma sessão com o banco de dados. Ele é responsável por interagir com o banco de dados e executar operações de banco de dados. Em nossa aplicação, usaremos o Entity Framework Core para interagir com nosso banco de dados.
Instalar os Pacotes Necessários
Antes de criar nosso contexto de banco de dados, precisamos instalar os seguintes pacotes:
-
Microsoft.EntityFrameworkCore
-
Microsoft.EntityFrameworkCore.SqlServer
-
FluentValidation.DependencyInjectionExtensions
Você pode instalar esses pacotes usando os seguintes comandos:
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package FluentValidation.DependencyInjectionExtensions
Verificar Instalação do Pacote
Para verificar se os pacotes estão instalados, abra o arquivo bookapi-minimal.csproj
no diretório raiz do seu projeto. Você deve ver os pacotes instalados listados da seguinte forma:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>bookapi_minimal</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>
Isso confirma que os pacotes foram instalados com sucesso.
Agora vamos criar nosso contexto de banco de dados.
Na pasta AppContext, crie um novo arquivo chamado ApplicationContext.cs
e adicione o seguinte código:
// AppContext/ApplicationContext.cs
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.AppContext
{
public class ApplicationContext(DbContextOptions<ApplicationContext> options) : DbContext(options)
{
// Esquema padrão para o contexto do banco de dados
private const string DefaultSchema = "bookapi";
// DbSet para representar a coleção de livros em nosso banco de dados
public DbSet<BookModel> Books { get; set; }
// Construtor para configurar o contexto do banco de dados
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(DefaultSchema);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);
}
}
}
Vamos desmembrar o código acima:
-
Definimos uma classe chamada
ApplicationContext
que herda deDbContext
. A classeDbContext
faz parte do Entity Framework Core e representa uma sessão com o banco de dados. -
O construtor aceita uma instância de
DbContextOptions<ApplicationContext>
. Este construtor é usado para configurar as opções do contexto do banco de dados. -
Definimos uma propriedade chamada
Books
do tipoDbSet<BookModel>
. Esta propriedade representa a coleção de livros em nosso banco de dados. -
Sobrescrevemos o método
OnModelCreating
para configurar o esquema do banco de dados e aplicar quaisquer configurações definidas em nossa aplicação.
Agora que criamos nosso contexto de banco de dados, vamos criar nosso método de extensão e registrar nosso contexto de banco de dados no contêiner de injeção de dependência.
Criar um Método de Extensão
Antes de criarmos o método de extensão, vamos entender o que é um método de extensão no contexto do ASP.NET Core.
Um método de extensão é um método estático que adiciona novas funcionalidades a um tipo existente sem modificar o tipo original. No ASP.NET Core, métodos de extensão são comumente usados para estender a funcionalidade da interface IServiceCollection
, que é usada para registrar serviços no contêiner de injeção de dependência.
Serviços são componentes que fornecem funcionalidade a uma aplicação, como acesso a banco de dados, logging e configuração. Ao criar um método de extensão para a interface IServiceCollection
, você pode simplificar o processo de registro de seus serviços no contêiner de injeção de dependência.
Em vez de colocar tudo no arquivo Program.cs
, criaremos um método de extensão para registrar nossos serviços no contêiner de injeção de dependência. Isso nos ajudará a manter nosso código limpo e organizado.
No diretório Extensions
, crie um novo arquivo chamado ServiceExtensions.cs
e adicione o seguinte código:
using System.Reflection;
using bookapi_minimal.AppContext;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.Extensions
{
public static class ServiceExtensions
{
public static void AddApplicationServices(this IHostApplicationBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (builder.Configuration == null) throw new ArgumentNullException(nameof(builder.Configuration));
// Adicionando o contexto do banco de dados
builder.Services.AddDbContext<ApplicationContext>(configure =>
{
configure.UseSqlServer(builder.Configuration.GetConnectionString("sqlConnection"));
});
// Adicionando validadores da assembleia atual
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
}
}
}
Vamos descrever o código acima:
-
Definimos uma classe estática chamada
ServiceExtensions
que contém um método de extensão chamadoAddApplicationServices
. Este método estende a interfaceIHostApplicationBuilder
, que é usada para configurar o pipeline de processamento de solicitações da aplicação. -
O método
AddApplicationServices
aceita uma instância deIHostApplicationBuilder
como parâmetro. Este parâmetro é usado para acessar a configuração e os serviços da aplicação. -
Adicionamos o
ApplicationContext
ao contêiner de injeção de dependência e configuramos para usar o SQL Server como provedor de banco de dados. Recuperamos a string de conexão do arquivoappsettings.json
usando o métodoGetConnectionString
. -
Adicionamos
validators
daassembly
atual usando o métodoAddValidatorsFromAssembly
. Este método varre a assembly atual em busca de classes que implementam a interface IValidator e as registra no contêiner de injeção de dependência.
Em seguida, precisamos adicionar a string de conexão ao arquivo appsettings.json
. Adicione o seguinte código ao seu arquivo appsettings.json
:
{
"ConnectionStrings": {
"sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
}
}
Certifique-se de substituir your_password
pela sua senha real do SQL Server.
Seu arquivo appsettings.json
deve ficar assim:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
},
"AllowedHosts": "*"
}
Parabéns! Você criou com sucesso o contexto do banco de dados, o método de extensão e a string de conexão para sua aplicação. Na próxima seção, criaremos um Contrato.
Como Criar um Contrato
Contratos são Objetos de Transferência de Dados (DTOs) que definem a estrutura dos dados trocados entre o cliente e o servidor. Em nossa aplicação, criaremos contratos para representar os dados enviados e recebidos pelos pontos de extremidade da nossa API.
Aqui estão os contratos que vamos criar:
-
CreateBookRequest: Isso representa os dados enviados ao criar um novo livro.
-
UpdateBookRequest: tHI Representa os dados enviados ao atualizar um livro existente.
-
BookResponse: Representa os dados retornados ao recuperar um livro.
-
ErrorResponse: Representa a resposta de erro retornada quando ocorre uma exceção.
-
ApiResponse: Representa a resposta retornada pela API.
Na pasta Contracts
, crie um novo arquivo chamado CreateBookRequest
e adicione o seguinte código:
// Contracts/CreateBookRequest.cs
namespace bookapi_minimal.Contracts
{
public record CreateBookRequest
{
public string Title { get; init; }
public string Author { get; init; }
public string Description { get; init; }
public string Category { get; init; }
public string Language { get; init; }
public int TotalPages { get; init; }
}
}
Na pasta Contracts
, crie um novo arquivo chamado UpdateBookRequest
e adicione o seguinte código:
// Contratos/UpdateBookRequest.cs
namespace bookapi_minimal.Contracts
{
public record UpdateBookRequest
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
No diretório Contratos
, crie um novo arquivo chamado BookResponse
e adicione o seguinte código:
// Contratos/BookResponse.cs
namespace bookapi_minimal.Contracts
{
public record BookResponse
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public int TotalPages { get; set; }
}
}
No diretório Contratos
, crie um novo arquivo chamado ErrorResponse
e adicione o seguinte código:
// Contratos/ErrorResponse.cs
namespace bookapi_minimal.Contracts
{
public record ErrorResponse
{
public string Title { get; set; }
public int StatusCode { get; set; }
public string Message { get; set; }
}
}
No diretório Contratos
, crie um novo arquivo chamado ApiResponse
e adicione o seguinte código:
// Contratos/ApiResponse.cs
namespace bookapi_minimal.Contracts
{
public class ApiResponse<T>
{
public T Data { get; set; }
public string Message { get; set; }
public ApiResponse(T data, string message)
{
Data = data;
Message = message;
}
}
}
Esses contratos nos ajudam a definir a estrutura dos dados trocados entre o cliente e o servidor, facilitando o trabalho com os dados em nossa aplicação.
Na próxima seção, criaremos serviços para implementar a lógica de negócios de nossa aplicação.
Como Adicionar Serviços
Serviços são componentes que fornecem funcionalidade a uma aplicação. Em nossa aplicação, criaremos serviços para implementar a lógica de negócios de nossa aplicação. Criaremos serviços para lidar com operações CRUD de livros, validar dados de livros e lidar com exceções.
No ASP.NET Core, os serviços são registrados no contêiner de injeção de dependência e podem ser injetados em outros componentes, como controladores e endpoints, mas como esta é uma API mínima, injetaremos os serviços diretamente nos endpoints.
Vamos criar uma interface para nossos serviços. Na pasta Interfaces
, crie um novo arquivo chamado IBookService.cs
e adicione o seguinte código:
// Interfaces/IBookService.cs
using bookapi_minimal.Contracts;
namespace bookapi_minimal.Interfaces
{
public interface IBookService
{
Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest);
Task<BookResponse> GetBookByIdAsync(Guid id);
Task<IEnumerable<BookResponse>> GetBooksAsync();
Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest updateBookRequest);
Task<bool> DeleteBookAsync(Guid id);
}
}
Vamos analisar o código acima: Definimos uma interface chamada IBookService
que contém métodos para lidar com operações CRUD para livros. A interface define os seguintes métodos:
-
AddBookAsync
: Adiciona um novo livro ao banco de dados. -
GetBookByIdAsync
: Recupera um livro pelo seu ID. -
GetBooksAsync
: Recupera todos os livros do banco de dados. -
UpdateBookAsync
: Atualiza um livro existente.
Estamos usando o Contrato que criamos anteriormente na pasta Contracts
. A interface IBookService
define a estrutura dos métodos que serão implementados pelas classes de serviço. Isso nos ajuda a separar a interface da implementação, facilitando a manutenção e o teste do nosso código.
Agora que criamos a interface, vamos criar a classe de serviço que implementa a interface.
Como Implementar o Serviço de Livro
Este serviço implementará a interface IBookService
e fornecerá a lógica de negócios para nossa aplicação. Na pasta Services
, crie um novo arquivo chamado BookService.cs
. Seu arquivo inicial deve ficar assim:
// Services/BookService.cs
namespace bookapi_minimal.Services
{
public class BookService
{
}
}
A primeira coisa que precisamos fazer é adicionar a interface à classe BookService
. Atualize a classe BookService
para implementar a interface IBookService
da seguinte forma:
// Services/BookService.cs
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Services
{
public class BookService:IBookService
{
}
}
Quando você fizer isso, seu VS Code pode mostrar um erro porque não implementamos os métodos na interface. Vamos em frente e implementar os métodos na classe BookService
.
No VS Code, você pode usar o atalho Ctrl + .
para implementar os métodos na interface. Então você verá o seguinte código gerado para você:
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Services
{
// Classe de serviço para gerenciar livros
public class BookService : IBookService
{
// Método para adicionar um novo livro ao banco de dados
public Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
throw new NotImplementedException();
}
// Método para excluir um livro do banco de dados
public Task<bool> DeleteBookAsync(Guid id)
{
throw new NotImplementedException();
}
// Método para obter um livro do banco de dados pelo seu ID
public Task<BookResponse> GetBookByIdAsync(Guid id)
{
throw new NotImplementedException();
}
// Método para obter todos os livros do banco de dados
public Task<IEnumerable<BookResponse>> GetBooksAsync()
{
throw new NotImplementedException();
}
// Método para atualizar um livro no banco de dados
public Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest updateBookRequest)
{
throw new NotImplementedException();
}
}
}
Agora você pode ver que os métodos na interface foram implementados na classe BookService
. Implementaremos a lógica de negócios para cada método na próxima seção.
Antes de fazer isso, vamos adicionar as dependências necessárias à classe BookService
. Precisamos injetar as dependências ApplicationContext
e ILogger
na classe BookService
. ApplicationContext
é usado para interagir com o banco de dados, enquanto ILogger
é usado para registro.
Para injetar as dependências, atualize a classe BookService
da seguinte forma:
// Services/BookService.cs
// ...
private readonly ApplicationContext _context; // Contexto do banco de dados
private readonly ILogger<BookService> _logger; // Logger para registrar informações e erros
//..
Como adicionamos as dependências, precisamos atualizar o construtor da BookService
para aceitar as dependências. Atualize o construtor da BookService
da seguinte forma:
// Services/BookService.cs
// ...
// Construtor para inicializar o contexto do banco de dados e o logger
public BookService(ApplicationContext context, ILogger<BookService> logger)
{
_context = context;
_logger = logger;
}
// ...
Agora que adicionamos as dependências e atualizamos o construtor, podemos implementar a lógica de negócios para cada método na classe BookService
.
Vamos criar a lógica para as operações de CRIAÇÃO, LEITURA, ATUALIZAÇÃO e EXCLUSÃO na classe BookService
.
Como Implementar o Método AddBookAsync
Como mencionei anteriormente, usaremos o método AddBookAsync
para adicionar um novo livro ao banco de dados. Neste método, criaremos uma nova entidade de livro, mapearemos os dados do objeto CreateBookRequest
para a entidade de livro e salvaremos a entidade de livro no banco de dados. Também retornaremos a entidade de livro como um objeto BookResponse
.
Atualize o método AddBookAsync
na classe BookService
da seguinte forma:
// Services/BookService.cs
// ...
/// <summary>
/// Adicionar um novo livro
/// </summary>
/// <param name="createBookRequest">Solicitação de livro a ser adicionada</param>
/// <returns>Detalhes do livro criado</returns>
public async Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
try
{
var book = new BookModel
{
Title = createBookRequest.Title,
Author = createBookRequest.Author,
Description = createBookRequest.Description,
Category = createBookRequest.Category,
Language = createBookRequest.Language,
TotalPages = createBookRequest.TotalPages
};
// Adicionar o livro ao banco de dados
_context.Books.Add(book);
await _context.SaveChangesAsync();
_logger.LogInformation("Book added successfully.");
// Retornar os detalhes do livro criado
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error adding book: {ex.Message}");
throw;
}
}
// ...
Neste código, estamos criando uma nova entidade de livro a partir do objeto CreateBookRequest
, mapeando os dados do objeto CreateBookRequest
para a entidade de livro, salvando a entidade de livro no banco de dados e retornando a entidade de livro como um objeto BookResponse
.
Também estamos registrando informações e erros usando a dependência ILogger
. Se ocorrer uma exceção durante o processo, registramos a mensagem de erro e relançamos a exceção.
Agora que implementamos o método AddBookAsync
, vamos implementar o método GetBookByIdAsync
.
Como Implementar o Método GetBookByIdAsync
O método GetBookByIdAsync
é usado para recuperar um livro pelo seu ID no banco de dados. Neste método, faremos uma consulta no banco de dados pelo livro com o ID especificado, mapearemos a entidade de livro para um objeto BookResponse
e retornaremos o objeto BookResponse
.
Atualize o método GetBookByIdAsync
na classe BookService
da seguinte forma:
// Serviços/BookService.cs
//...
/// <summary>
/// Obter um livro pelo seu ID
/// </summary>
/// <param name="id">ID do livro</param>
/// <returns>Detalhes do livro</returns>
public async Task<BookResponse> GetBookByIdAsync(Guid id)
{
try
{
// Encontrar o livro pelo seu ID
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// Retornar os detalhes do livro
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving book: {ex.Message}");
throw;
}
}
//...
Neste código, estamos consultando o banco de dados pelo livro com o ID especificado, mapeando a entidade do livro para um objeto BookResponse
e retornando o objeto BookResponse
. Também estamos registrando informações e erros usando a dependência ILogger
.
Se o livro com o ID especificado não for encontrado, registramos uma mensagem de aviso e retornamos null. Se ocorrer uma exceção durante o processo, registramos a mensagem de erro e relançamos a exceção.
Agora que implementamos o método GetBookByIdAsync
, vamos implementar o método GetBooksAsync
.
Como Implementar o Método GetBooksAsync
O método GetBooksAsync
é usado para recuperar todos os livros do banco de dados. Neste método, faremos uma consulta ao banco de dados para todos os livros, mapearemos cada entidade de livro para um objeto BookResponse
e retornaremos uma lista de objetos BookResponse
.
Atualize o método GetBooksAsync
na classe BookService
da seguinte forma:
// Services/BookService.cs
//...
/// <summary>
/// Obter todos os livros
/// </summary>
/// <returns>Lista de todos os livros</returns>
public async Task<IEnumerable<BookResponse>> GetBooksAsync()
{
try
{
// Obter todos os livros do banco de dados
var books = await _context.Books.ToListAsync();
// Retornar os detalhes de todos os livros
return books.Select(book => new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
});
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving books: {ex.Message}");
throw;
}
}
//...
Aqui, estamos consultando o banco de dados para todos os livros, mapeando cada entidade de livro para um objeto BookResponse
e retornando uma lista de objetos BookResponse
. Também estamos registrando informações e erros usando a dependência ILogger
. Se ocorrer uma exceção durante o processo, registramos a mensagem de erro e relançamos a exceção.
Agora que implementamos o método GetBooksAsync
, vamos implementar o método UpdateBookAsync
.
Como Implementar o Método UpdateBookAsync
O método UpdateBookAsync
é usado para atualizar um livro existente no banco de dados. Neste método, faremos uma consulta no banco de dados pelo livro com o ID especificado, atualizaremos a entidade do livro com os dados do objeto UpdateBookRequest
, salvaremos a entidade do livro atualizada no banco de dados e retornaremos a entidade do livro atualizada como um objeto BookResponse
.
Atualize o método UpdateBookAsync
na classe BookService
da seguinte forma:
// Services/BookService.cs
//...
/// <summary>
/// Atualizar um livro existente
/// </summary>
/// <param name="id">ID do livro a ser atualizado</param>
/// <param name="book">Modelo do livro atualizado</param>
/// <returns>Detalhes do livro atualizado</returns>
public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
{
try
{
// Encontre o livro existente pelo seu ID
var existingBook = await _context.Books.FindAsync(id);
if (existingBook == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// Atualize os detalhes do livro
existingBook.Title = book.Title;
existingBook.Author = book.Author;
existingBook.Description = book.Description;
existingBook.Category = book.Category;
existingBook.Language = book.Language;
existingBook.TotalPages = book.TotalPages;
// Salve as alterações no banco de dados
await _context.SaveChangesAsync();
_logger.LogInformation("Book updated successfully.");
// Retorne os detalhes do livro atualizado
return new BookResponse
{
Id = existingBook.Id,
Title = existingBook.Title,
Author = existingBook.Author,
Description = existingBook.Description,
Category = existingBook.Category,
Language = existingBook.Language,
TotalPages = existingBook.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error updating book: {ex.Message}");
throw;
}
}
//...
Aqui, estamos consultando o banco de dados pelo livro com o ID especificado, atualizando a entidade do livro com os dados do objeto UpdateBookRequest
, salvando a entidade do livro atualizada no banco de dados e retornando a entidade do livro atualizada como um objeto BookResponse
. Também estamos registrando informações e erros usando a dependência ILogger
.
Se o livro com o ID especificado não for encontrado, registramos uma mensagem de aviso e retornamos null. Se ocorrer uma exceção durante o processo, registramos a mensagem de erro e relançamos a exceção.
Agora que implementamos o método UpdateBookAsync
, vamos implementar o método DeleteBookAsync
.
Como Implementar o Método DeleteBookAsync
O método DeleteBookAsync
é usado para excluir um livro existente do banco de dados. Neste método, consultaremos o banco de dados pelo livro com o ID especificado, removeremos a entidade do livro do banco de dados e retornaremos um valor booleano indicando se o livro foi excluído com sucesso.
Atualize o método DeleteBookAsync
na classe BookService
da seguinte forma:
// Serviços/BookService.cs
//...
/// <summary>
/// Excluir um livro pelo seu ID
/// </summary>
/// <param name="id">ID do livro a ser excluído</param>
/// <returns>True se o livro foi excluído, false caso contrário</returns>
public async Task<bool> DeleteBookAsync(Guid id)
{
try
{
// Encontrar o livro pelo seu ID
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return false;
}
// Remover o livro do banco de dados
_context.Books.Remove(book);
await _context.SaveChangesAsync();
_logger.LogInformation($"Book with ID {id} deleted successfully.");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Error deleting book: {ex.Message}");
throw;
}
}
//...
Neste código, estamos consultando o banco de dados pelo livro com o ID especificado, removendo a entidade do livro do banco de dados e retornando um valor booleano indicando se o livro foi excluído com sucesso. Também estamos registrando informações e erros usando a dependência ILogger
.
Se o livro com o ID especificado não for encontrado, registramos uma mensagem de aviso e retornamos false. Se ocorrer uma exceção durante o processo, registramos a mensagem de erro e relançamos a exceção.
Agora você implementou com sucesso a lógica de negócios para os métodos AddBookAsync
, GetBookByIdAsync
, GetBooksAsync
, UpdateBookAsync
e DeleteBookAsync
na classe BookService
. Esses métodos lidam com as operações CRUD para livros, validam os dados dos livros e tratam exceções. Até agora, sua classe BookService
deve se parecer com isso:
using bookapi_minimal.AppContext;
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
namespace bookapi_minimal.Services
{
public class BookService : IBookService
{
private readonly ApplicationContext _context; // Contexto do banco de dados
private readonly ILogger<BookService> _logger; // Logger para registrar informações e erros
// Construtor para inicializar o contexto do banco de dados e o logger
public BookService(ApplicationContext context, ILogger<BookService> logger)
{
_context = context;
_logger = logger;
}
/// Adicionar um novo livro
/// </summary>
/// <param name="createBookRequest">Requisição de livro a ser adicionada</param>
/// <returns>Detalhes do livro criado</returns>
public async Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
{
try
{
var book = new BookModel
{
Title = createBookRequest.Title,
Author = createBookRequest.Author,
Description = createBookRequest.Description,
Category = createBookRequest.Category,
Language = createBookRequest.Language,
TotalPages = createBookRequest.TotalPages
};
// Adicionar o livro ao banco de dados
_context.Books.Add(book);
await _context.SaveChangesAsync();
_logger.LogInformation("Book added successfully.");
// Retornar os detalhes do livro criado
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error adding book: {ex.Message}");
throw;
}
}
/// <summary>
/// Obter um livro pelo seu ID
/// </summary>
/// <param name="id">ID do livro</param>
/// <returns>Detalhes do livro</returns>
public async Task<BookResponse> GetBookByIdAsync(Guid id)
{
try
{
// Encontrar o livro pelo seu ID
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// Retornar os detalhes do livro
return new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving book: {ex.Message}");
throw;
}
}
/// <summary>
/// Obter todos os livros
/// </summary>
/// <returns>Lista de todos os livros</returns>
public async Task<IEnumerable<BookResponse>> GetBooksAsync()
{
try
{
// Obter todos os livros do banco de dados
var books = await _context.Books.ToListAsync();
// Retornar os detalhes de todos os livros
return books.Select(book => new BookResponse
{
Id = book.Id,
Title = book.Title,
Author = book.Author,
Description = book.Description,
Category = book.Category,
Language = book.Language,
TotalPages = book.TotalPages
});
}
catch (Exception ex)
{
_logger.LogError($"Error retrieving books: {ex.Message}");
throw;
}
}
/// <summary>
/// Atualizar um livro existente
/// </summary>
/// <param name="id">ID do livro a ser atualizado</param>
/// <param name="book">Modelo do livro atualizado</param>
/// <returns>Detalhes do livro atualizado</returns>
public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
{
try
{
// Encontrar o livro existente pelo seu ID
var existingBook = await _context.Books.FindAsync(id);
if (existingBook == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return null;
}
// Atualizar os detalhes do livro
existingBook.Title = book.Title;
existingBook.Author = book.Author;
existingBook.Description = book.Description;
existingBook.Category = book.Category;
existingBook.Language = book.Language;
existingBook.TotalPages = book.TotalPages;
// Salvar as alterações no banco de dados
await _context.SaveChangesAsync();
_logger.LogInformation("Book updated successfully.");
// Retornar os detalhes do livro atualizado
return new BookResponse
{
Id = existingBook.Id,
Title = existingBook.Title,
Author = existingBook.Author,
Description = existingBook.Description,
Category = existingBook.Category,
Language = existingBook.Language,
TotalPages = existingBook.TotalPages
};
}
catch (Exception ex)
{
_logger.LogError($"Error updating book: {ex.Message}");
throw;
}
}
/// <summary>
/// Excluir um livro pelo seu ID
/// </summary>
/// <param name="id">ID do livro a ser excluído</param>
/// <returns>True se o livro foi excluído, false caso contrário</returns>
public async Task<bool> DeleteBookAsync(Guid id)
{
try
{
// Encontrar o livro pelo seu ID
var book = await _context.Books.FindAsync(id);
if (book == null)
{
_logger.LogWarning($"Book with ID {id} not found.");
return false;
}
// Remover o livro do banco de dados
_context.Books.Remove(book);
await _context.SaveChangesAsync();
_logger.LogInformation($"Book with ID {id} deleted successfully.");
return true;
}
catch (Exception ex)
{
_logger.LogError($"Error deleting book: {ex.Message}");
throw;
}
}
}
}
Parabéns! Você implementou com sucesso a lógica de negócios para os métodos AddBookAsync
, GetBookByIdAsync
, GetBooksAsync
, UpdateBookAsync
e DeleteBookAsync
na classe BookService
.
Há uma coisa que precisamos fazer: precisamos registrar o serviço em nosso método de extensão. Vamos adiante e fazer isso.
No seu arquivo ServiceExtensions.cs
, adicione o seguinte código:
// Extensions/ServiceExtensions.cs
//..
builder.Services.AddScoped<IBookService, BookService>();
//...
Isso registrará a classe BookService
como um serviço scoped. Isso significa que o serviço será criado uma vez por solicitação e descartado após a conclusão da solicitação.
Agora que temos o serviço funcionando, vamos adiante e criar as classes de exceção.
Como Criar Exceções
Manusear corretamente as exceções é crucial para garantir a estabilidade e confiabilidade de um aplicativo. No contexto do ASP.NET Core, há dois tipos principais de exceções:
-
Exceções do Sistema: Estas são exceções lançadas pelo tempo de execução do .NET ou pelo sistema subjacente.
-
Exceções da Aplicação: Estas são exceções lançadas pelo código da aplicação para lidar com erros ou condições específicas.
No ASP.NET Core com .NET 8, foi introduzida uma nova funcionalidade chamada tratamento de exceções global. Esta funcionalidade permite que você lide com exceções globalmente em sua aplicação, facilitando a gestão de erros e proporcionando uma experiência de usuário consistente.
Em nossa aplicação, criaremos classes de exceção personalizadas para lidar com erros e condições específicas. Também utilizaremos a funcionalidade de tratamento de exceções global para gerenciar exceções globalmente, garantindo uma abordagem uniforme para o tratamento de erros em toda a aplicação.
Vamos criar as seguintes classes de exceção:
-
NoBookFoundException
: Lançada quando um livro com o ID especificado não é encontrado. -
BookDoesNotExistException
: Lançada quando um livro com o ID especificado não existe. -
GlobalExceptionHandler
: Lida com exceções globalmente na aplicação.
No diretório Exceptions
, crie um novo arquivo chamado NoBookFoundException.cs
e adicione o seguinte código:
// Exceptions/NoBookFoundException.cs
namespace bookapi_minimal.Exceptions
{
public class NoBookFoundException : Exception
{
public NoBookFoundException() : base("No books found")
{}
}
}
Neste código, estamos criando uma classe de exceção personalizada chamada NoBookFoundException
que herda da classe Exception
. A classe NoBookFoundException
é usada para lidar com o cenário em que nenhum livro é encontrado no banco de dados. Também estamos fornecendo uma mensagem de erro personalizada para a exceção.
No diretório Exceptions
, crie um novo arquivo chamado BookDoesNotExistException.cs
e adicione o seguinte código:
namespace bookapi_minimal.Exceptions
{
public class BookDoesNotExistException : Exception
{
private int id { get; set; }
public BookDoesNotExistException(int id) : base($"Book with id {id} does not exist")
{
this.id = id;
}
}
}
Neste código, estamos criando uma classe de exceção personalizada chamada BookDoesNotExistException
que herda da classe Exception
. A classe BookDoesNotExistException
é usada para lidar com o cenário em que um livro com o ID especificado não existe no banco de dados. Também estamos fornecendo uma mensagem de erro personalizada para a exceção.
No diretório Exceptions
, crie um novo arquivo chamado GlobalExceptionHandler.cs
e adicione o seguinte código:
// Exceções/GlobalExceptionHandler.cs
using System.Net;
using bookapi_minimal.Contracts;
using Microsoft.AspNetCore.Diagnostics;
namespace bookapi_minimal.Exceptions
{
// Classe de manipulador de exceções global implementando IExceptionHandler
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
// Construtor para inicializar o logger
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
// Método para manipular exceções assincronamente
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
// Registrar os detalhes da exceção
_logger.LogError(exception, "An error occurred while processing your request");
var errorResponse = new ErrorResponse
{
Message = exception.Message,
Title = exception.GetType().Name
};
// Determinar o código de status com base no tipo de exceção
switch (exception)
{
case BadHttpRequestException:
errorResponse.StatusCode = (int)HttpStatusCode.BadRequest;
break;
case NoBookFoundException:
case BookDoesNotExistException:
errorResponse.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
errorResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
// Definir o código de status da resposta
httpContext.Response.StatusCode = errorResponse.StatusCode;
// Escrever a resposta de erro como JSON
await httpContext.Response.WriteAsJsonAsync(errorResponse, cancellationToken);
// Retornar verdadeiro para indicar que a exceção foi manipulada
return true;
}
}
}
Vamos desmembrar o código acima:
-
Definimos uma classe chamada
GlobalExceptionHandler
que implementa a interfaceIExceptionHandler
. A interfaceIExceptionHandler
é usada para manipular exceções globalmente na aplicação. -
A classe
GlobalExceptionHandler
contém um construtor que inicializa a dependênciaILogger<GlobalExceptionHandler>
. OILogger
é usado para registrar informações e erros. -
O método
TryHandleAsync
é usado para lidar com exceções de forma assíncrona. Este método aceita oHttpContext
,Exception
eCancellationToken
como parâmetros. -
Registramos os detalhes da exceção usando a dependência
ILogger
. -
Criamos um objeto
ErrorResponse
para representar a resposta de erro retornada pela API. O objetoErrorResponse
contém a mensagem de erro, título e código de status. -
Determinamos o código de status com base no tipo de exceção. Se a exceção for uma
BadHttpRequestException
, definimos o código de status comoBadRequest
. Se a exceção for umaNoBookFoundException
ouBookDoesNotExistException
, definimos o código de status comoNotFound
. Caso contrário, definimos o código de status comoInternalServerError
. -
Definimos o código de status da resposta usando a propriedade
httpContext.Response.StatusCode
. -
Escrevemos a resposta de erro como JSON usando o método
httpContext.Response.WriteAsJsonAsync
. -
Retornamos
true
para indicar que a exceção foi tratada com sucesso.
Agora que criamos as classes de exceção, vamos registrar o GlobalExceptionHandler
no contêiner de injeção de dependência. Como criamos um método de extensão para registrar serviços no contêiner de injeção de dependência, adicionaremos o GlobalExceptionHandler
à classe ServiceExtensions
.
Atualize a classe ServiceExtensions
na pasta Extensions
da seguinte forma:
// Extensions/ServiceExtensions.cs
//...
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
//...
O método AddExceptionHandler
registra o GlobalExceptionHandler
no contêiner de injeção de dependência. O método AddProblemDetails
registra a classe ProblemDetails
no contêiner de injeção de dependência.
Agora que registramos o GlobalExceptionHandler
no contêiner de injeção de dependência, podemos usá-lo para lidar com exceções globalmente em nossa aplicação. Na próxima seção, criaremos os endpoints da API para interagir com os dados do livro.
Como Criar os Endpoints da API
No contexto de APIs mínimas em ASP.NET Core, há muitas maneiras de configurar seus endpoints.
Você pode defini-los diretamente no arquivo Program.cs
. Mas à medida que seu projeto cresce e você precisa adicionar mais endpoints ou funcionalidades, é útil organizar melhor seu código. Uma maneira de alcançar isso é criando uma classe separada para lidar com todos os endpoints.
Como discutimos acima, APIs mínimas não usam controladores ou visualizações como as aplicações tradicionais de ASP.NET Core. Em vez disso, elas usam métodos como MapGet
, MapPost
, MapPut
e MapDelete
para definir métodos HTTP e rotas para os endpoints da API.
Para começar, navegue até a pasta Endpoints
e crie um novo arquivo chamado BookEndpoints.cs
. Adicione o seguinte código ao arquivo:
// Endpoints/BookEndpoints.cs
namespace bookapi_minimal.Endpoints
{
public static class BookEndPoint
{
public static IEndpointRouteBuilder MapBookEndPoint(this IEndpointRouteBuilder app)
{
return app;
}
}
}
A classe BookEndpoints
contém um método MapBookEndPoint
que retorna um objeto IEndpointRouteBuilder
. O objeto IEndpointRouteBuilder
é usado para definir os métodos HTTP e as rotas para os endpoints da API. Nas próximas seções, definiremos os endpoints da API para criar
, ler
, atualizar
e excluir
livros.
Como Criar o Endpoint AddBookAsync
de Livros
Nesta seção, criaremos o endpoint AddBookAsync
. Este endpoint aceitará um objeto Book
como uma carga útil JSON e o adicionará ao banco de dados. Usaremos o método MapPost
para definir o método HTTP e a rota para este endpoint.
Adicione o seguinte código à classe BookEndpoints
:
// Endpoints/BookEndpoints.cs
//...
// Endpoint para adicionar um novo livro
app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
{
var result = await bookService.AddBookAsync(createBookRequest);
return Results.Created($"/books/{result.Id}", result);
});
//...
-
Definição da Rota: O método MapPost define a rota para o endpoint como
/books
. -
Modelo de Requisição: O endpoint aceita um objeto
CreateBookRequest
como uma carga útil JSON. O objetoCreateBookRequest
contém os dados necessários para criar um novo livro. -
Modelo de Resposta: O endpoint retorna um objeto
Book
como carga útil JSON. O objetoBook
contém os dados do livro recém-criado. -
Valor de Retorno: O endpoint retorna um resultado
Created
. O resultadoCreated
contém a localização do livro recém-criado e o objetoBook
.
Como Criar o Endpoint de Livro GetBookAsync
Nesta seção, criaremos o endpoint GetBookAsync
. Este endpoint aceitará um ID de livro como parâmetro de consulta e retornará o livro com o ID especificado. Usaremos o método MapGet
para definir o método HTTP e a rota para este endpoint.
Adicione o seguinte código à classe BookEndpoints
:
// Endpoints/BookEndpoints.cs
// ...
// Endpoint para obter todos os livros
app.MapGet("/books", async (IBookService bookService) =>
{
var result = await bookService.GetBooksAsync();
return Results.Ok(result);
});
//...
-
Definição de Rota: O método MapGet define a rota para o endpoint como
/books
. -
Modelo de Requisição: O endpoint aceita um objeto
Book
como carga útil JSON. O objetoBook
contém os dados necessários para criar um novo livro. -
Modelo de Resposta: O endpoint retorna um objeto
Book
como carga útil JSON. O objetoBook
contém os dados do livro recém-criado. -
Valor de Retorno: O endpoint retorna um resultado
Ok
. O resultadoOk
contém o objetoBook
.
Como Criar o Endpoint de Livro GetBookByIdAsync
Nesta seção, criaremos o endpoint GetBookByIdAsync
. Este endpoint aceitará um ID de livro como um parâmetro de rota e retornará o livro com o ID especificado. Usaremos o método MapGet
para definir o método HTTP e a rota para este endpoint.
Adicione o seguinte código à classe BookEndpoints
:
// Endpoints/BookEndpoints.cs
//...
// Endpoint para obter um livro por ID
app.MapGet("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.GetBookByIdAsync(id);
return result != null ? Results.Ok(result) : Results.NotFound();
});
//...
-
Definição da Rota: O método MapGet define a rota para o endpoint como
/books/{id:guid}
. O parâmetro{id:guid}
especifica que o parâmetroid
deve ser um GUID. -
Modelo de Requisição: O endpoint aceita um objeto
Book
como uma carga útil JSON. O objetoBook
contém os dados necessários para criar um novo livro. -
Modelo de Resposta: O endpoint retorna um objeto
Book
como uma carga útil JSON. O objetoBook
contém os dados do livro recém-criado. -
Valor de Retorno: O endpoint retorna um resultado
Ok
se o livro for encontrado. O resultadoNotFound
é retornado se o livro não for encontrado.
Como Criar o Endpoint UpdateBookAsync
para Livros
Nesta seção, criaremos o endpoint UpdateBookAsync
. Este endpoint aceitará um ID de livro como parâmetro de rota e um objeto Book
como carga útil JSON e atualizará o livro com o ID especificado. Usaremos o método MapPut
para definir o método HTTP e a rota para este endpoint.
Adicione o seguinte código à classe BookEndpoints
:
// Endpoints/BookEndpoints.cs
//...
// Endpoint para atualizar um livro por ID
app.MapPut("/books/{id:guid}", async (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =>
{
var result = await bookService.UpdateBookAsync(id, updateBookRequest);
return result != null ? Results.Ok(result) : Results.NotFound();
});
//...
-
Definição de Rota: O método MapPut define a rota para o endpoint como
/books/{id:guid}
. O parâmetro{id:guid}
especifica que o parâmetroid
deve ser um GUID. -
Modelo de Requisição: O endpoint aceita um objeto
Book
como carga útil JSON. O objetoBook
contém os dados necessários para criar um novo livro. -
Modelo de Resposta: O endpoint retorna um objeto
Book
como carga útil JSON. O objetoBook
contém os dados do livro recém-criado. -
Valor de Retorno: O endpoint retorna um resultado
Ok
se o livro for encontrado. O resultadoNotFound
é retornado se o livro não for encontrado.
Como Criar o Endpoint DeleteBookAsync
para Livro
Nesta seção, criaremos o endpoint DeleteBookAsync
. Este endpoint aceitará um ID de livro como um parâmetro de rota e excluirá o livro com o ID especificado. Usaremos o método MapDelete
para definir o método HTTP e a rota para este endpoint.
Adicione o seguinte código à classe BookEndpoints
:
// Endpoints/BookEndpoints.cs
//...
// Endpoint para excluir um livro por ID
app.MapDelete("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.DeleteBookAsync(id);
return result ? Results.NoContent() : Results.NotFound();
});
//...
-
Definição da Rota: O método MapDelete define a rota para o endpoint como
/books/{id:guid}
. O parâmetro{id:guid}
especifica que o parâmetroid
deve ser um GUID. -
Modelo de Requisição: O endpoint aceita um objeto
Book
como uma carga útil JSON. O objetoBook
contém os dados necessários para criar um novo livro. -
Modelo de Resposta: O endpoint retorna um objeto
Book
como uma carga útil JSON. O objetoBook
contém os dados do livro recém-criado. -
Valor de Retorno: O endpoint retorna um resultado
NoContent
se o livro for excluído com sucesso. O resultadoNotFound
é retornado se o livro não for encontrado.
Agora definimos todos os métodos para os endpoints do livro. Portanto, sua classe de endpoint deve ficar assim:
// Endpoints/BookEndpoints.cs
using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;
namespace bookapi_minimal.Endpoints
{
public static class BookEndPoint
{
public static IEndpointRouteBuilder MapBookEndPoint(this IEndpointRouteBuilder app)
{
// Defina os endpoints
// Endpoint para adicionar um novo livro
app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
{
var result = await bookService.AddBookAsync(createBookRequest);
return Results.Created($"/books/{result.Id}", result);
});
// Endpoint para obter todos os livros
app.MapGet("/books", async (IBookService bookService) =>
{
var result = await bookService.GetBooksAsync();
return Results.Ok(result);
});
// Endpoint para obter um livro por ID
app.MapGet("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.GetBookByIdAsync(id);
return result != null ? Results.Ok(result) : Results.NotFound();
});
// Endpoint para atualizar um livro por ID
app.MapPut("/books/{id:guid}", async (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =>
{
var result = await bookService.UpdateBookAsync(id, updateBookRequest);
return result != null ? Results.Ok(result) : Results.NotFound();
});
// Endpoint para excluir um livro por ID
app.MapDelete("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.DeleteBookAsync(id);
return result ? Results.NoContent() : Results.NotFound();
});
return app;
}
}
}
Parabéns! Você criou todos os endpoints para a API do livro. Os endpoints lidam com as operações CRUD para livros e retornam as respostas apropriadas com base na solicitação e nos dados.
Como Registrar os Endpoints
Depois de definir os pontos de extremidade da API do livro, o próximo passo é registrar esses pontos de extremidade no arquivo Program.cs
. Usaremos o método MapBookEndpoints
para registrar os pontos de extremidade do livro.
Também devemos organizar nossa classe Program.cs
para garantir que ela permaneça organizada e manutenível.
// Program.cs
using System.Reflection;
using bookapi_minimal.Endpoints;
using bookapi_minimal.Services;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.AddApplicationServices();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c=>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Mimal API", Version = "v1", Description = "Showing how you can build minimal " +
"api with .net" });
// Definir o caminho dos comentários para o JSON e a interface do usuário do Swagger.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
// Configurar o pipeline de solicitações HTTP.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseExceptionHandler();
app.MapGroup("/api/v1/")
.WithTags(" Book endpoints")
.MapBookEndPoint();
app.Run();
Vamos descrever os componentes principais do arquivo Program.cs
:
-
AddApplicationServices: Este método registra os serviços necessários para a API. É um método de extensão que criamos anteriormente para adicionar serviços ao contêiner de injeção de dependência.
-
AddSwaggerGen: Este método registra o gerador de Swagger, que é usado para criar a documentação de Swagger para a API. Especificamos o título, versão e descrição da API no documento de Swagger.
-
MapGroup: Este método agrupa os pontos de extremidade. Ele recebe um caminho como parâmetro e retorna um objeto
IEndpointRouteBuilder
. Usamos o métodoWithTags
para adicionar tags aos pontos de extremidade e o métodoMapBookEndpoints
para registrar os pontos de extremidade do livro. -
Run: Este método inicia a aplicação.
Para habilitar a documentação do Swagger, você precisa adicionar a propriedade GenerateDocumentationFile
ao seu arquivo .csproj
. Neste exemplo, o arquivo é chamado bookapi-minimal.csproj
, mas o nome pode variar com base no seu projeto.
Adicione a seguinte linha ao seu arquivo .csproj
:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
No final, o bookapi-minimal.csproj deve ficar assim:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>bookapi_minimal</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>
Agora que registramos os pontos de extremidade do livro no arquivo Program.cs
, podemos executar a aplicação e testar os pontos de extremidade da API usando o Swagger.
Quando você executar o aplicativo, deverá ver a documentação do Swagger no seguinte URL: https://localhost:5001/swagger/index.html
. A documentação do Swagger fornece informações sobre os endpoints da API, modelos de solicitação e resposta, e permite que você teste os endpoints diretamente do navegador. Você deverá ver algo como isso:
Parabéns! Você implementou a lógica de negócios para o serviço de livros, criou exceções personalizadas, definiu endpoints da API e registrou os endpoints no arquivo Program.cs
. Você também habilitou a documentação do Swagger para testar os endpoints da API.
Como Adicionar Dados Iniciais ao Banco de Dados
Outro passo importante é preencher o banco de dados com dados iniciais quando o aplicativo iniciar. Esses dados iniciais irão popular o banco de dados, permitindo que você teste seus endpoints da API sem adicionar dados manualmente.
Vamos adicionar alguns dados iniciais antes de realizar migrações e testar nossos endpoints da API.
Para isso, criaremos uma nova classe em nossa pasta Configuration chamada BookTypeConfigurations
e adicionaremos o seguinte código:
using bookapi_minimal.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace bookapi_minimal.Configurations
{
public class BookTypeConfigurations : IEntityTypeConfiguration<BookModel>
{
public void Configure(EntityTypeBuilder<BookModel> builder)
{
// Configurar o nome da tabela
builder.ToTable("Books");
// Configurar a chave primária
builder.HasKey(x => x.Id);
// Configurar propriedades
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.Property(x => x.Title).IsRequired().HasMaxLength(100);
builder.Property(x => x.Author).IsRequired().HasMaxLength(100);
builder.Property(x => x.Description).IsRequired().HasMaxLength(500);
builder.Property(x => x.Category).IsRequired().HasMaxLength(100);
builder.Property(x => x.Language).IsRequired().HasMaxLength(50);
builder.Property(x => x.TotalPages).IsRequired();
// Dados iniciais
builder.HasData(
new BookModel
{
Id = Guid.NewGuid(),
Title = "The Alchemist",
Author = "Paulo Coelho",
Description = "The Alchemist follows the journey of an Andalusian shepherd",
Category = "Fiction",
Language = "English",
TotalPages = 208
},
new BookModel
{
Id = Guid.NewGuid(),
Title = "To Kill a Mockingbird",
Author = "Harper Lee",
Description = "A novel about the serious issues of rape and racial inequality.",
Category = "Fiction",
Language = "English",
TotalPages = 281
},
new BookModel
{
Id = Guid.NewGuid(),
Title = "1984",
Author = "George Orwell",
Description = "A dystopian social science fiction novel and cautionary tale about the dangers of totalitarianism. ",
Category = "Fiction",
Language = "English",
TotalPages = 328
}
);
}
}
}
Vamos analisar o código acima:
No Entity Framework Core, você pode usar a interface IEntityTypeConfiguration
para configurar o tipo de entidade e os dados de种子 para o banco de dados. A classe BookTypeConfigurations
implementa a interface IEntityTypeConfiguration<BookModel>
e fornece a configuração para a entidade BookModel
.
-
Método Configure: Este método é usado para configurar o tipo de entidade
BookModel
. Ele define o nome da tabela, a chave primária e as propriedades para a entidadeBookModel
.-
Nome da Tabela: O método
ToTable
especifica o nome da tabela a ser criada no banco de dados. Neste caso, o nome da tabela é definido como “Books”. -
Chave Primária: O método
HasKey
especifica a chave primária para a entidadeBookModel
. A chave primária é definida como a propriedadeId
. -
Propriedades: O método
Property
configura as propriedades da entidadeBookModel
. Ele especifica o tipo de dado, o comprimento e as restrições para cada propriedade.
-
-
Dados Iniciais: O método
HasData
semeia o banco de dados com dados iniciais. Ele cria três objetosBookModel
com dados de exemplo para testar os pontos de extremidade da API.
Agora que criamos a classe BookTypeConfigurations
, precisamos registrar essa configuração na classe ApplicationContext
. Isso garante que a configuração seja aplicada quando o banco de dados é criado ou migrado.
Finalmente estamos quase prontos para testar nossa API. Mas antes de fazer isso, precisamos realizar migrações para criar o banco de dados e aplicar os dados de seed.
Lembram que adicionamos nossa string de conexão com o banco de dados no arquivo appsettings.json
? Agora vamos realizar uma migração e posteriormente atualizar nosso banco de dados para que a migração tenha efeito.
Como Realizar uma Migração
Migrações permitem que você atualize o esquema do banco de dados com base nas mudanças feitas em suas classes de modelo. No Entity Framework Core, você pode usar o comando dotnet ef migrations add
para criar uma nova migração refletindo essas mudanças.
Para realizar uma migração, execute o seguinte comando no terminal:
dotnet ef migrations add InitialCreate
Se o comando for bem-sucedido, você deverá ver uma saída semelhante a esta:
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
Agora você verá uma nova pasta chamada Migrations
em seu projeto. Esta pasta contém os arquivos de migração que foram criados com base nas mudanças feitas em suas classes de modelo. Esses arquivos de migração incluem os comandos SQL necessários para atualizar o esquema do banco de dados.
Como Atualizar o Banco de Dados
Depois de criar a migração, você precisa aplicar a migração para atualizar o esquema do banco de dados. Você pode usar o comando dotnet ef database update
para aplicar a migração e atualizar o banco de dados. Certifique-se de que o SQL Server está em execução.
Execute o seguinte comando no terminal:
dotnet ef database update
Isso atualizará o esquema do banco de dados com base nas mudanças feitas em suas classes de modelo. Certifique-se de que não há erros em sua string de conexão com o banco de dados.
Como Testar os Endpoints da API
Agora podemos testar nossos endpoints usando o Swagger. Para isso, execute a aplicação digitando o seguinte comando no terminal:
dotnet run
Isso executará nossa aplicação. Você pode abrir seu navegador e navegar até https://localhost:5001/swagger/index.html
para acessar a documentação do Swagger. Você deverá ver uma lista de endpoints da API, modelos de solicitação e resposta, e a capacidade de testar os endpoints diretamente do navegador.
Se o número da porta for diferente de 5001
, não se preocupe – ainda funcionará. A porta pode mudar dependendo do tipo de máquina que você está usando, mas ainda assim alcançará o mesmo resultado.
Como Testar o Endpoint Get All Books
Para testar o endpoint Get All Books
, siga estas etapas:
-
Na documentação do Swagger, clique no endpoint
GET /api/v1/books
. -
Clique no botão
Try it out
. -
Clique no botão
Execute
.
Isso enviará uma solicitação à API para recuperar todos os livros no banco de dados.
Você deverá ver a resposta da API, que incluirá a lista de livros que foram semeados no banco de dados.
A imagem abaixo mostra a resposta da API:
Como Testar o Endpoint Obter Livro por ID
Para testar o endpoint Obter Livro por ID
, siga estas etapas:
-
Na documentação do Swagger, clique no endpoint
GET /api/v1/books/{id}
. -
Insira o ID de um livro no campo
id
. Você pode usar um dos IDs de livros que foi preenchido no banco de dados. -
Clique no botão
Experimente
.
Isso enviará uma solicitação à API para recuperar o livro com o ID especificado. Você deve ver a resposta da API, que incluirá o livro com o ID especificado.
A imagem abaixo mostra a resposta da API:
Como Testar o Endpoint Adicionar Livro
Para testar o endpoint Adicionar Livro
, siga estas etapas:
-
Na documentação do Swagger, clique no endpoint
POST /api/v1/books
. -
Clique no botão
Experimente
. -
Insira os detalhes do livro no corpo da solicitação.
-
Clique no botão
Executar
.
Isso enviará uma solicitação para a API para adicionar um novo livro ao banco de dados.
Você deverá ver a resposta da API, que incluirá o livro recém-criado.
A imagem abaixo mostra a resposta da API:
Como Testar o Endpoint Atualizar Livro
Para testar o endpoint Atualizar Livro
, siga estas etapas:
-
Na documentação do Swagger, clique no endpoint
PUT /api/v1/books/{id}
. -
Digite o ID de um livro no campo
id
. Você pode usar o ID de um dos livros que acabamos de adicionar. -
Clique no botão
Experimente
.
Isso enviará uma solicitação para a API para atualizar o livro com o ID especificado.
Você deverá ver a resposta da API, que incluirá o livro atualizado.
A imagem abaixo mostra a resposta da API:
Como Testar o Endpoint Excluir Livro
Para testar o endpoint Excluir Livro
, siga estas etapas:
-
Na documentação do Swagger, clique no endpoint
DELETE /api/v1/books/{id}
. -
Digite o ID de um livro no campo
id
. Você pode usar qualquer um dos ids dos livros que acabamos de adicionar ou dos dados inicializados. -
Clique no botão
Experimente
.
Isso enviará uma solicitação à API para excluir o livro com o ID especificado.
A imagem abaixo mostra a resposta da API:
Parabéns! Você implementou todas as operações CRUD para livros e testou os endpoints da API usando Swagger, verificando que eles funcionam conforme o esperado. Agora você pode construir sobre esta base para adicionar mais recursos e funcionalidades à sua API.
Conclusão
Este manual explorou como criar uma API mínima em ASP.NET Core com .NET 8. Construímos uma API de livros abrangente que suporta operações CRUD, implementamos exceções personalizadas, definimos e registramos endpoints da API e habilitamos a documentação da Swagger para facilitar os testes.
Seguindo este tutorial, você adquiriu uma base sólida para construir APIs mínimas com ASP.NET Core. Agora você pode aplicar este conhecimento e criar APIs robustas para diversos domínios e indústrias.
Espero que você tenha achado este tutorial útil e informativo. Obrigado por ler!
Sinta-se à vontade para se conectar comigo nas redes sociais:
Source:
https://www.freecodecamp.org/news/create-a-minimal-api-in-net-core-handbook/