Las API mínimas son una característica emocionante introducida en .NET 6, diseñada para revolucionar la forma en que creas APIs.

Imagina construir APIs robustas con código mínimo y cero plantillas—no más lidiar con controladores, enrutamiento o middleware. Eso es lo que las API mínimas te permiten hacer. La idea con estas APIs es simplificar el proceso de desarrollo, haciéndolo increíblemente fácil y eficiente.

En este artículo, profundizaremos en el mundo de las API mínimas en .NET 8 y te guiará a través de la creación de una API de librería completamente funcional. Aprenderás a obtener todos los libros, recuperar un libro por su ID, agregar nuevos libros e incluso eliminar libros. Comencemos.

Tabla de Contenidos

Requisitos Previos

Antes de comenzar, asegúrate de tener los siguientes requisitos previos instalados en tu máquina:

Alternativamente, puedes usar Visual Studio 2022, que viene con soporte integrado para .NET 8. Pero en este artículo, usaremos Visual Studio Code. Es ligero, fácil de usar y multiplataforma.

Usaremos Swagger UI para probar nuestra API. Swagger UI es una herramienta poderosa que te permite interactuar con tu API directamente desde tu navegador. Proporciona una interfaz amigable para probar los endpoints de tu API, facilitando la prueba y depuración de la misma.

Cuando crees un nuevo proyecto, automáticamente instalará los paquetes necesarios y configurará el proyecto para usar Swagger UI. .NET 8 incluye Swagger UI por defecto, así que ya sea que crees tu aplicación en Visual Studio o con .NET, Swagger UI estará configurado para ti.

Ejecuta tu aplicación y Swagger UI se abrirá automáticamente en tu navegador – pero dado que estamos usando VS Code, necesitamos hacer clic en el número de puerto en nuestra terminal.

Puedes encontrar el código fuente de este proyecto en GitHub.

Introducción a las API Mínimas

Imagina trabajar en una base de código con numerosos puntos de conexión, lo que la hace bastante grande y compleja. Tradicionalmente, construir una API en ASP.NET Core implica el uso de controladores, enrutamiento, middleware y una cantidad significativa de código repetitivo. Sin embargo, hay dos enfoques para construir una API en ASP.NET Core: la forma tradicional y la forma mínima.

La forma tradicional es familiar para la mayoría de los desarrolladores, ya que implica el uso de controladores y un código de infraestructura extenso. La forma mínima, introducida en .NET 6, permite crear APIs con un código mínimo y cero código repetitivo. Este enfoque simplifica el proceso de desarrollo, permitiéndote enfocarte en escribir lógica de negocio en lugar de lidiar con código de infraestructura.

Las API Mínimas son ligeras, rápidas y perfectas para construir APIs de pequeño a mediano tamaño. Son ideales para prototipar, construir microservicios o crear APIs simples que no requieran mucha complejidad. En este manual, exploraremos el mundo de las API mínimas en .NET 6 y aprenderemos a crear una API funcional de librería desde cero.

Cómo Crear una API Mínima

Crear una API mínima es sencillo cuando se utiliza el CLI de dotnet, ya que la plantilla predeterminada ya es una API mínima. Pero si usas Visual Studio, necesitarás eliminar el código repetitivo que viene con la plantilla del proyecto.

Vamos a empezar usando el CLI de .NET para crear un proyecto de API mínima.


dotnet new webapi  -n BookStoreApi

El comando dotnet new webapi crea un nuevo proyecto de API mínima llamado BookStoreApi. Este proyecto contiene los archivos y carpetas necesarios para comenzar.

Vamos a explorar la estructura del proyecto:

  • Program.cs: El punto de entrada de la aplicación, donde se configura el host.

  • bookapi-minimal.sln: El archivo de solución que contiene el proyecto.

  • bookapi-minimal.http: Un archivo que contiene solicitudes HTTP de muestra para probar la API.

  • bookapi-minimal.csproj: El archivo de proyecto que contiene la configuración del proyecto.

  • appsettings.json: El archivo de configuración que almacena la configuración de la aplicación.

  • appsettings.Development.json: El archivo de configuración para el entorno de desarrollo.

Al abrir el archivo program.cs, notarás que el código es mínimo. El archivo Program.cs contiene el siguiente código:


var builder = WebApplication.CreateBuilder(args);

// Agrega servicios al contenedor.
// Más información sobre cómo configurar Swagger/OpenAPI en https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configura el pipeline de solicitudes 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);
}

Si aún no entiendes completamente el código, no te preocupes—lo cubriremos en detalle en las próximas secciones. Lo más importante es que las API mínimas requieren muy poco código, lo cual es una de sus principales ventajas.

El código predeterminado configura una API sencilla de pronóstico del tiempo que puedes usar para probar tu configuración. Genera una lista de pronósticos del tiempo y los devuelve cuando realizas una solicitud GET al endpoint /weatherforecast. Además, el código incluye Swagger UI para ayudarte a probar la API.

Presta especial atención al método app.MapGet, que mapea una ruta a una función manejadora. En este caso, mapea la ruta /weatherforecast a una función que devuelve una lista de pronósticos del tiempo. Usaremos métodos similares para crear nuestros propios endpoints en las siguientes secciones.

Antes de comenzar a crear la estructura de carpetas de nuestro proyecto, comprendamos los métodos HTTP tanto en las API basadas en controladores como en las API mínimas.

Métodos HTTP en API basadas en controladores y API mínimas

En un enfoque basado en controladores, que es la forma tradicional de crear web APIs, necesitas crear una clase de controlador y definir métodos para cada método HTTP. Por ejemplo:

  • Para crear un método GET, utilizas el atributo [HttpGet].

  • Para crear un método POST, utilizas el atributo [HttpPost].

  • Para crear un método PUT, utilizas el atributo [HttpPut].

  • Para crear un método DELETE, utilizas el atributo [HttpDelete].

Así es como se crean los puntos de conexión en un enfoque basado en Controladores.

En contraste, las API Mínimas utilizan métodos como app.MapGet, app.MapPost, app.MapPut y app.MapDelete para crear puntos de conexión. Esta es la principal diferencia entre los dos enfoques: las API basadas en Controladores usan atributos para definir puntos de conexión, mientras que las API Mínimas usan métodos.

Ahora que entiendes cómo manejar solicitudes HTTP tanto en API basadas en Controladores como en API Mínimas, vamos a crear la estructura de carpetas de nuestro proyecto.

Antes de crear la estructura de carpetas de nuestro proyecto, primero ejecutemos lo que tenemos. Como aprendimos anteriormente, cuando creas un proyecto con Visual Studio o .NET CLI, viene con un proyecto predeterminado WeatherForecast que podemos ejecutar y ver en la UI. Ejecutémoslo para asegurarnos de que todo funcione antes de pasar a crear la carpeta de nuestro proyecto.

Ejecuta este comando:


dotnet run

Deberías ver la siguiente salida:

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

Esto significa que la aplicación está en ejecución y escuchando en http://localhost:5228. Como mencioné anteriormente, dado que estamos usando dotnet CLI y Visual Studio Code, la aplicación no abrirá automáticamente el navegador por nosotros. Necesitamos hacerlo manualmente.

Abre tu navegador y navega a http://localhost:5228/swagger/index.html para ver la respuesta predeterminada de la API.

Deberías ver algo como esto:

Ahora, lo siguiente que debemos hacer es encontrar una manera de estructurar nuestro proyecto y crear los archivos y carpetas necesarios para empezar.

Archivos del Proyecto de API Mínima

Para organizar nuestro proyecto, crearemos una jerarquía de carpetas estructurada. Esto ayudará a mantener nuestro código limpio y mantenible. Aquí está la estructura de carpetas que usaremos:

  • AppContext: Contiene el contexto de la base de datos y las configuraciones relacionadas.

  • Configuraciones: Contiene las configuraciones de Entity Framework Core y los datos de inicio para la base de datos.

  • Contratos: Contiene Objetos de Transferencia de Datos (DTO) utilizados en nuestra aplicación.

  • Endpoints: Donde definimos y configuramos nuestros endpoints mínimos de API.

  • Excepciones: Contiene clases de excepción personalizadas utilizadas en el proyecto.

  • Extensiones: Contiene métodos de extensión que utilizaremos en todo el proyecto.

  • Modelos: Contiene modelos de lógica de negocio.

  • Servicios: Contiene clases de servicio que implementan la lógica de negocio.

  • Interfaces: Contiene definiciones de interfaces utilizadas para mapear nuestros servicios.

En Visual Studio Code, puedes crear esta estructura de carpetas de la siguiente manera:

- AppContext
- Configurations
- Contracts
- Endpoints
- Exceptions
- Extensions
- Models
- Services
- Interfaces

Después de la configuración, la estructura de carpetas de tu proyecto debería verse así:

Ahora que nuestra estructura de proyecto está configurada, podemos seguir adelante y comenzar a escribir nuestro código. Empecemos por crear nuestros modelos.

Cómo Crear los Modelos

En esta sección, crearemos modelos para nuestra aplicación. Los modelos son los bloques de construcción de nuestra aplicación, representando los datos con los que trabajará nuestra aplicación. Para nuestro ejemplo, crearemos un modelo para un libro.

Para comenzar, crea una carpeta llamada Models en el directorio de tu proyecto. Dentro de esta carpeta, crea un archivo llamado BookModel.cs y añade el siguiente 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 clase BookModel define las propiedades que representan los detalles de un libro, como su título, autor, descripción, categoría, idioma y número total de páginas. Cada propiedad está diseñada para contener información específica sobre el libro, facilitando la gestión y manipulación de los datos del libro dentro de nuestra aplicación.

Ahora que hemos creado nuestro modelo, vamos a crear nuestro contexto de base de datos.

Cómo Crear el Contexto de Base de Datos

El contexto de base de datos es una clase que representa una sesión con la base de datos. Es responsable de interactuar con la base de datos y ejecutar operaciones de base de datos. En nuestra aplicación, utilizaremos Entity Framework Core para interactuar con nuestra base de datos.

Instalar los Paquetes Requeridos

Antes de crear nuestro contexto de base de datos, necesitamos instalar los siguientes paquetes:

Puedes instalar estos paquetes usando los siguientes 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 la Instalación de Paquetes

Para verificar que los paquetes están instalados, abre el archivo bookapi-minimal.csproj en el directorio raíz de tu proyecto. Deberías ver los paquetes instalados listados de la siguiente manera:

<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>

Esto confirma que los paquetes han sido instalados correctamente.

Ahora vamos a crear nuestro contexto de base de datos.

En la carpeta AppContext, cree un nuevo archivo llamado ApplicationContext.cs y agregue el siguiente 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 predeterminado para el contexto de la base de datos
        private const string DefaultSchema = "bookapi";


       // DbSet para representar la colección de libros en nuestra base de datos
        public DbSet<BookModel> Books { get; set; }

        // Constructor para configurar el contexto de la base de datos

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.HasDefaultSchema(DefaultSchema);

            modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);

            modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationContext).Assembly);

        }

    }
}

Desglosemos el código anterior:

  • Definimos una clase llamada ApplicationContext que hereda de DbContext. La clase DbContext es parte de Entity Framework Core y representa una sesión con la base de datos.

  • El constructor acepta una instancia de DbContextOptions<ApplicationContext>. Este constructor se utiliza para configurar las opciones del contexto de la base de datos.

  • Definimos una propiedad llamada Books de tipo DbSet<BookModel>. Esta propiedad representa la colección de libros en nuestra base de datos.

  • Sobrescribimos el método OnModelCreating para configurar el esquema de la base de datos y aplicar cualquier configuración definida en nuestra aplicación.

Ahora que hemos creado nuestro contexto de base de datos, vamos a crear nuestro método de extensión y registrar nuestro contexto de base de datos en el contenedor de inyección de dependencias.

Crear un Método de Extensión

Antes de crear el método de extensión, vamos a entender qué es un método de extensión en el contexto de ASP.NET Core.

Un método de extensión es un método estático que añade nueva funcionalidad a un tipo existente sin modificar el tipo original. En ASP.NET Core, los métodos de extensión se utilizan comúnmente para extender la funcionalidad de la interfaz IServiceCollection, que se utiliza para registrar servicios en el contenedor de inyección de dependencias.

Los servicios son componentes que proporcionan funcionalidad a una aplicación, como acceso a bases de datos, registro y configuración. Al crear un método de extensión para la interfaz IServiceCollection, puedes simplificar el proceso de registro de tus servicios en el contenedor de inyección de dependencias.

En lugar de poner todo en el archivo Program.cs, crearemos un método de extensión para registrar nuestros servicios en el contenedor de inyección de dependencias. Esto nos ayudará a mantener nuestro código limpio y organizado.

En la carpeta Extensions, crea un nuevo archivo llamado ServiceExtensions.cs y agrega el siguiente 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));

            // Agregando el contexto de la base de datos
            builder.Services.AddDbContext<ApplicationContext>(configure =>
            {
                configure.UseSqlServer(builder.Configuration.GetConnectionString("sqlConnection"));
            });

            // Agregando validadores de la asamblea actual
            builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
        }
    }
}

Vamos a desglosar el código anterior:

  • Definimos una clase estática llamada ServiceExtensions que contiene un método de extensión llamado AddApplicationServices. Este método extiende la interfaz IHostApplicationBuilder, que se utiliza para configurar el pipeline de procesamiento de solicitudes de la aplicación.

  • El método AddApplicationServices acepta una instancia de IHostApplicationBuilder como parámetro. Este parámetro se utiliza para acceder a la configuración y servicios de la aplicación.

  • Añadimos el ApplicationContext al contenedor de inyección de dependencias y lo configuramos para usar SQL Server como proveedor de base de datos. Recuperamos la cadena de conexión desde el archivo appsettings.json utilizando el método GetConnectionString.

  • Añadimos validadores desde el ensamblado actual utilizando el método AddValidatorsFromAssembly. Este método escanea el ensamblado actual en busca de clases que implementen la interfaz IValidator y las registra en el contenedor de inyección de dependencias.

A continuación, necesitamos añadir la cadena de conexión al archivo appsettings.json. Añada el siguiente código a su archivo appsettings.json:

{ 
     "ConnectionStrings": {
    "sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
  }
  }

Asegúrese de reemplazar your_password con su contraseña real de SQL Server.

Su archivo appsettings.json debería verse así:


{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "sqlConnection": "Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"
  },
  "AllowedHosts": "*"
}

Felicidades! Ha creado con éxito el contexto de base de datos, el método de extensión y la cadena de conexión para su aplicación. En la siguiente sección, crearemos un Contrato.

Cómo Crear un Contrato

Los contratos son Objetos de Transferencia de Datos (DTOs) que definen la estructura de los datos intercambiados entre el cliente y el servidor. En nuestra aplicación, crearemos contratos para representar los datos enviados y recibidos por nuestros puntos de conexión de la API.

Aquí están los contratos que vamos a crear:

  • CreateBookRequest: Esto representa los datos enviados al crear un nuevo libro.

  • UpdateBookRequest: tHI Representa los datos enviados al actualizar un libro existente.

  • BookResponse: Representa los datos devueltos al recuperar un libro.

  • ErrorResponse: Representa la respuesta de error devuelta cuando ocurre una excepción.

  • ApiResponse: Representa la respuesta devuelta por la API.

En la carpeta Contracts, crea un nuevo archivo llamado CreateBookRequest y agrega el siguiente 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; }
    }
}

En la carpeta Contracts, crea un nuevo archivo llamado UpdateBookRequest y agrega el siguiente 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; }

    }
}

En la carpeta Contratos, crea un nuevo archivo llamado BookResponse y añade el siguiente 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; }
    }
}

En la carpeta Contratos, crea un nuevo archivo llamado ErrorResponse y añade el siguiente 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; }

    }

}

En la carpeta Contratos, crea un nuevo archivo llamado ApiResponse y añade el siguiente 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;
        }
    }
}

Estos contratos nos ayudan a definir la estructura de los datos intercambiados entre el cliente y el servidor, facilitando el trabajo con los datos en nuestra aplicación.

En la siguiente sección, crearemos servicios para implementar la lógica de negocio de nuestra aplicación.

Cómo Añadir Servicios

Los servicios son componentes que proporcionan funcionalidad a una aplicación. En nuestra aplicación, crearemos servicios para implementar la lógica de negocio de nuestra aplicación. Crearemos servicios para manejar operaciones CRUD para libros, validar datos de libros y manejar excepciones.

En ASP.NET Core, los servicios se registran en el contenedor de inyección de dependencias y pueden ser inyectados en otros componentes, como controladores y endpoints, pero esta es una API mínima, por lo que inyectaremos los servicios directamente en los endpoints.

Vamos a crear una interfaz para nuestros servicios. En la carpeta Interfaces, crea un nuevo archivo llamado IBookService.cs y añade el siguiente 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 a desglosar el código anterior: Hemos definido una interfaz llamada IBookService que contiene métodos para manejar operaciones CRUD para libros. La interfaz define los siguientes métodos:

  • AddBookAsync: Añade un nuevo libro a la base de datos.

  • GetBookByIdAsync: Recupera un libro por su ID.

  • GetBooksAsync: Recupera todos los libros de la base de datos.

  • UpdateBookAsync: Actualiza un libro existente.

Estamos utilizando el Contrato que creamos anteriormente en la carpeta Contracts. La interfaz IBookService define la estructura de los métodos que serán implementados por las clases de servicio. Esto nos ayuda a separar la interfaz de la implementación, facilitando el mantenimiento y la prueba de nuestro código.

Ahora que hemos creado la interfaz, vamos a crear la clase de servicio que implementa la interfaz.

Cómo Implementar el Servicio de Libros

Este servicio implementará la interfaz IBookService y proporcionará la lógica de negocio para nuestra aplicación. En la carpeta Services, cree un nuevo archivo llamado BookService.cs. Su archivo inicial debería verse así:


// Services/BookService.cs

namespace bookapi_minimal.Services
{
    public class BookService
    {

    }
}

Lo primero que necesitamos hacer es agregar la interfaz a la clase BookService. Actualice la clase BookService para implementar la interfaz IBookService de la siguiente manera:



// Services/BookService.cs



using bookapi_minimal.Interfaces;

namespace bookapi_minimal.Services
{
    public class BookService:IBookService
    {

    }
}

Cuando haga esto, su VS Code podría mostrar un error porque no hemos implementado los métodos en la interfaz. Procedamos a implementar los métodos en la clase BookService.

En VS Code puede usar el atajo Ctrl + . para implementar los métodos en la interfaz. Luego verá el siguiente código generado para usted:


using bookapi_minimal.Contracts;
using bookapi_minimal.Interfaces;

namespace bookapi_minimal.Services
{
     // Clase de servicio para gestionar libros
   public class BookService : IBookService
   {
       // Método para agregar un nuevo libro a la base de datos
       public Task<BookResponse> AddBookAsync(CreateBookRequest createBookRequest)
       {
           throw new NotImplementedException();
       }

      // Método para eliminar un libro de la base de datos
       public Task<bool> DeleteBookAsync(Guid id)
       {
           throw new NotImplementedException();
       }

       // Método para obtener un libro de la base de datos por su ID

       public Task<BookResponse> GetBookByIdAsync(Guid id)
       {
           throw new NotImplementedException();
       }

      // Método para obtener todos los libros de la base de datos
       public Task<IEnumerable<BookResponse>> GetBooksAsync()
       {
           throw new NotImplementedException();
       }

       // Método para actualizar un libro en la base de datos
       public Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest updateBookRequest)
       {
           throw new NotImplementedException();
       }
   }
}

Ahora puedes ver que los métodos en la interfaz han sido implementados en la clase BookService. Implementaremos la lógica de negocio para cada método en la siguiente sección.

Antes de hacerlo, agreguemos las dependencias necesarias a la clase BookService. Necesitamos inyectar las dependencias de ApplicationContext y ILogger en la clase BookService. ApplicationContext se utiliza para interactuar con la base de datos, mientras que ILogger se utiliza para el registro.

Para inyectar las dependencias, actualiza la clase BookService de la siguiente manera:


// Services/BookService.cs

// ...
 private readonly ApplicationContext _context; // Contexto de la base de datos
  private readonly ILogger<BookService> _logger; // Registrador para información y errores

//..

Dado que hemos agregado las dependencias, necesitamos actualizar el constructor de BookService para aceptar las dependencias. Actualiza el constructor de BookService de la siguiente manera:


// Services/BookService.cs

// ...

  // Constructor para inicializar el contexto de la base de datos y el registrador
 public BookService(ApplicationContext context, ILogger<BookService> logger)
 {
            _context = context;
            _logger = logger;
}

// ...

Ahora que hemos agregado las dependencias y actualizado el constructor, podemos implementar la lógica de negocio para cada método en la clase BookService.

Vamos a crear la lógica para las operaciones de CREAR, LEER, ACTUALIZAR y ELIMINAR en la clase BookService.

Cómo Implementar el Método AddBookAsync

Como mencioné anteriormente, usaremos el método AddBookAsync para agregar un nuevo libro a la base de datos. En este método, crearemos una nueva entidad de libro, mapearemos los datos del objeto CreateBookRequest a la entidad de libro, y guardaremos la entidad de libro en la base de datos. También devolveremos la entidad de libro como un objeto BookResponse.

Actualiza el método AddBookAsync en la clase BookService de la siguiente manera:

// Services/BookService.cs

// ...
 /// <summary>
        /// Agregar un nuevo libro
        /// </summary>
        /// <param name="createBookRequest">Solicitud de libro a ser agregada</param>
        /// <returns>Detalles del libro creado</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
                };

                // Agregar el libro a la base de datos
                _context.Books.Add(book);
                await _context.SaveChangesAsync();
                _logger.LogInformation("Book added successfully.");

                // Devolver los detalles del libro creado
                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;
            }
        }
// ...

En este código, estamos creando una nueva entidad de libro a partir del objeto CreateBookRequest, mapeando los datos del objeto CreateBookRequest a la entidad de libro, guardando la entidad de libro en la base de datos y devolviendo la entidad de libro como un objeto BookResponse.

También estamos registrando información y errores utilizando la dependencia ILogger. Si ocurre una excepción durante el proceso, registramos el mensaje de error y relanzamos la excepción.

Ahora que hemos implementado el método AddBookAsync, vamos a implementar el método GetBookByIdAsync.

Cómo Implementar el Método GetBookByIdAsync

El método GetBookByIdAsync se utiliza para recuperar un libro por su ID desde la base de datos. En este método, consultaremos la base de datos para el libro con el ID especificado, mapearemos la entidad de libro a un objeto BookResponse y devolveremos el objeto BookResponse.

Actualice el método GetBookByIdAsync en la clase BookService de la siguiente manera:


// Servicios/BookService.cs

//... 

    /// <summary>
        /// Obtener un libro por su ID
        /// </summary>
        /// <param name="id">ID del libro</param>
        /// <returns>Detalles del libro</returns>
        public async Task<BookResponse>  GetBookByIdAsync(Guid id)
        {
            try
            {
                // Encontrar el libro por su ID
                var book = await _context.Books.FindAsync(id);
                if (book == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return null;
                }

                // Devolver los detalles del libro
                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;
            }
        }

//...

En este código, estamos consultando la base de datos para el libro con el ID especificado, mapeando la entidad del libro a un objeto BookResponse y devolviendo el objeto BookResponse. También estamos registrando información y errores utilizando la dependencia ILogger.

Si no se encuentra el libro con el ID especificado, registramos un mensaje de advertencia y devolvemos null. Si ocurre una excepción durante el proceso, registramos el mensaje de error y relanzamos la excepción.

Ahora que hemos implementado el método GetBookByIdAsync, vamos a implementar el método GetBooksAsync.

Cómo Implementar el Método GetBooksAsync

El método GetBooksAsync se utiliza para recuperar todos los libros de la base de datos. En este método, consultaremos la base de datos para obtener todos los libros, mapearemos cada entidad de libro a un objeto BookResponse y devolveremos una lista de objetos BookResponse.

Actualiza el método GetBooksAsync en la clase BookService de la siguiente manera:



// Services/BookService.cs

//... 


  /// <summary>
        /// Obtener todos los libros
        /// </summary>
        /// <returns>Lista de todos los libros</returns>
        public async Task<IEnumerable<BookResponse>> GetBooksAsync()
        {
            try
            {
                // Obtener todos los libros de la base de datos
                var books = await _context.Books.ToListAsync();

                // Devolver los detalles de todos los libros
                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;
            }
        }
//...

Aquí, estamos consultando la base de datos para obtener todos los libros, mapeando cada entidad de libro a un objeto BookResponse y devolviendo una lista de objetos BookResponse. También estamos registrando información y errores utilizando la dependencia ILogger. Si ocurre una excepción durante el proceso, registramos el mensaje de error y relanzamos la excepción.

Ahora que hemos implementado el método GetBooksAsync, implementemos el método UpdateBookAsync.

Cómo Implementar el Método UpdateBookAsync

El método UpdateBookAsync se utiliza para actualizar un libro existente en la base de datos. En este método, consultaremos la base de datos para obtener el libro con el ID especificado, actualizaremos la entidad del libro con los datos del objeto UpdateBookRequest, guardaremos la entidad del libro actualizada en la base de datos y devolveremos la entidad del libro actualizada como un objeto BookResponse.

Actualiza el método UpdateBookAsync en la clase BookService de la siguiente manera:

// Services/BookService.cs
 //...
 /// <summary>
        /// Actualizar un libro existente
        /// </summary>
        /// <param name="id">ID del libro a actualizar</param>
        /// <param name="book">Modelo del libro actualizado</param>
        /// <returns>Detalles del libro actualizado</returns>
        public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
        {
            try
            {
                // Encontrar el libro existente por su ID
                var existingBook = await _context.Books.FindAsync(id);
                if (existingBook == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return null;
                }

                // Actualizar los detalles del libro
                existingBook.Title = book.Title;
                existingBook.Author = book.Author;
                existingBook.Description = book.Description;
                existingBook.Category = book.Category;
                existingBook.Language = book.Language;
                existingBook.TotalPages = book.TotalPages;

                // Guardar los cambios en la base de datos
                await _context.SaveChangesAsync();
                _logger.LogInformation("Book updated successfully.");

                // Devolver los detalles del libro actualizado
                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;
            }
        }
//...

Aquí, estamos consultando la base de datos para el libro con el ID especificado, actualizando la entidad del libro con los datos del objeto UpdateBookRequest, guardando la entidad del libro actualizada en la base de datos y devolviendo la entidad del libro actualizada como un objeto BookResponse. También estamos registrando información y errores utilizando la dependencia ILogger.

Si no se encuentra el libro con el ID especificado, registramos un mensaje de advertencia y devolvemos null. Si ocurre una excepción durante el proceso, registramos el mensaje de error y relanzamos la excepción.

Ahora que hemos implementado el método UpdateBookAsync, vamos a implementar el método DeleteBookAsync.

Cómo Implementar el Método DeleteBookAsync

El método DeleteBookAsync se utiliza para eliminar un libro existente de la base de datos. En este método, consultaremos la base de datos para el libro con el ID especificado, eliminaremos la entidad del libro de la base de datos y devolveremos un valor booleano que indique si el libro se eliminó correctamente.

Actualice el método DeleteBookAsync en la clase BookService de la siguiente manera:

// Servicios/BookService.cs

 //...


/// <summary>
        /// Elimina un libro por su ID
        /// </summary>
        /// <param name="id">ID del libro que se va a eliminar</param>
        /// <returns>True si el libro fue eliminado, false en caso contrario</returns>
        public async Task<bool> DeleteBookAsync(Guid id)
        {
            try
            {
                // Encuentra el libro por su ID
                var book = await _context.Books.FindAsync(id);
                if (book == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return false;
                }

                // Elimina el libro de la base de datos
                _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;
            }
        }
//...

En este código, estamos consultando la base de datos para encontrar el libro con el ID especificado, eliminando la entidad del libro de la base de datos y devolviendo un valor booleano que indica si el libro fue eliminado con éxito. También estamos registrando información y errores utilizando la dependencia ILogger.

Si no se encuentra el libro con el ID especificado, registramos un mensaje de advertencia y devolvemos false. Si ocurre una excepción durante el proceso, registramos el mensaje de error y relanzamos la excepción.

Ahora has implementado con éxito la lógica de negocio para los métodos AddBookAsync, GetBookByIdAsync, GetBooksAsync, UpdateBookAsync y DeleteBookAsync en la clase BookService. Estos métodos manejan las operaciones CRUD para los libros, validan los datos de los libros y gestionan las excepciones. Hasta ahora, tu clase BookService debería verse así:



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 de la base de datos
        private readonly ILogger<BookService> _logger; // Registrador para información y errores
          // Constructor para inicializar el contexto de la base de datos y el registrador
        public BookService(ApplicationContext context, ILogger<BookService> logger)
        {
            _context = context;
            _logger = logger;
        }

           /// Agregar un nuevo libro
        /// </summary>
        /// <param name="createBookRequest">Solicitud de libro a agregar</param>
        /// <returns>Detalles del libro creado</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
                };

                // Agregar el libro a la base de datos
                _context.Books.Add(book);
                await _context.SaveChangesAsync();
                _logger.LogInformation("Book added successfully.");

                // Retornar los detalles del libro creado
                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>
        /// Obtener un libro por su ID
        /// </summary>
        /// <param name="id">ID del libro</param>
        /// <returns>Detalles del libro</returns>
        public async Task<BookResponse>  GetBookByIdAsync(Guid id)
        {
            try
            {
                // Encontrar el libro por su ID
                var book = await _context.Books.FindAsync(id);
                if (book == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return null;
                }

                // Retornar los detalles del libro
                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>
        /// Obtener todos los libros
        /// </summary>
        /// <returns>Lista de todos los libros</returns>
        public async Task<IEnumerable<BookResponse>> GetBooksAsync()
        {
            try
            {
                // Obtener todos los libros de la base de datos
                var books = await _context.Books.ToListAsync();

                // Retornar los detalles de todos los libros
                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>
        /// Actualizar un libro existente
        /// </summary>
        /// <param name="id">ID del libro a actualizar</param>
        /// <param name="book">Modelo de libro actualizado</param>
        /// <returns>Detalles del libro actualizado</returns>
        public async Task<BookResponse> UpdateBookAsync(Guid id, UpdateBookRequest book)
        {
            try
            {
                // Encontrar el libro existente por su ID
                var existingBook = await _context.Books.FindAsync(id);
                if (existingBook == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return null;
                }

                // Actualizar los detalles del libro
                existingBook.Title = book.Title;
                existingBook.Author = book.Author;
                existingBook.Description = book.Description;
                existingBook.Category = book.Category;
                existingBook.Language = book.Language;
                existingBook.TotalPages = book.TotalPages;

                // Guardar los cambios en la base de datos
                await _context.SaveChangesAsync();
                _logger.LogInformation("Book updated successfully.");

                // Retornar los detalles del libro actualizado
                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>
        /// Eliminar un libro por su ID
        /// </summary>
        /// <param name="id">ID del libro a eliminar</param>
        /// <returns>True si el libro fue eliminado, false en caso contrario</returns>
        public async Task<bool> DeleteBookAsync(Guid id)
        {
            try
            {
                // Encontrar el libro por su ID
                var book = await _context.Books.FindAsync(id);
                if (book == null)
                {
                    _logger.LogWarning($"Book with ID {id} not found.");
                    return false;
                }

                // Eliminar el libro de la base de datos
                _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;
            }
        }

    }
}

¡Felicidades! Has implementado con éxito la lógica de negocio para los métodos AddBookAsync, GetBookByIdAsync, GetBooksAsync, UpdateBookAsync y DeleteBookAsync en la clase BookService.

Hay una cosa que necesitamos hacer: necesitamos registrar el servicio en nuestro método de extensión. Vamos a hacerlo.

En tu archivo ServiceExtensions.cs, agrega el siguiente código:


// Extensions/ServiceExtensions.cs

//..

 builder.Services.AddScoped<IBookService, BookService>();
//...

Esto registrará la clase BookService como un servicio de ámbito. Esto significa que el servicio se creará una vez por solicitud y se liberará después de que la solicitud esté completa.

Ahora que el servicio está funcionando, sigamos adelante y creemos las clases de excepción.

Cómo Crear Excepciones

Manejar adecuadamente las excepciones es crucial para garantizar la estabilidad y fiabilidad de una aplicación. En el contexto de ASP.NET Core, hay dos tipos principales de excepciones:

  • Excepciones del Sistema: Estas son excepciones lanzadas por el tiempo de ejecución de .NET o el sistema subyacente.

  • Excepciones de la Aplicación: Estas son excepciones lanzadas por el código de la aplicación para manejar errores o condiciones específicas.

En ASP.NET Core con .NET 8, se introdujo una nueva característica llamada manejo de excepciones globales. Esta característica permite manejar excepciones de manera global en tu aplicación, facilitando la gestión de errores y proporcionando una experiencia de usuario consistente.

En nuestra aplicación, crearemos clases de excepción personalizadas para manejar errores y condiciones específicas. También aprovecharemos la característica de manejo de excepciones globales para gestionar excepciones de manera global, asegurando un enfoque uniforme en el manejo de errores en toda la aplicación.

Vamos a crear las siguientes clases de excepción:

  • NoBookFoundException: Lanzada cuando no se encuentra un libro con el ID especificado.

  • BookDoesNotExistException: Lanzada cuando un libro con el ID especificado no existe.

  • GlobalExceptionHandler: Maneja excepciones de manera global en la aplicación.

En la carpeta Exceptions, crea un nuevo archivo llamado NoBookFoundException.cs y agrega el siguiente código:


// Exceptions/NoBookFoundException.cs

namespace bookapi_minimal.Exceptions
{

    public class NoBookFoundException : Exception
    {

        public NoBookFoundException() : base("No books found")
        {}
    }
}

En este código, estamos creando una clase de excepción personalizada llamada NoBookFoundException que hereda de la clase Exception. La clase NoBookFoundException se utiliza para manejar el escenario en el que no se encuentran libros en la base de datos. También estamos proporcionando un mensaje de error personalizado para la excepción.

En la carpeta Exceptions, crea un nuevo archivo llamado BookDoesNotExistException.cs y agrega el siguiente 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;
        } 

    }
}

En este código, estamos creando una clase de excepción personalizada llamada BookDoesNotExistException que hereda de la clase Exception. La clase BookDoesNotExistException se utiliza para manejar el escenario en el que un libro con el ID especificado no existe en la base de datos. También estamos proporcionando un mensaje de error personalizado para la excepción.

En la carpeta Exceptions, crea un nuevo archivo llamado GlobalExceptionHandler.cs y agrega el siguiente código:

// Excepciones/GlobalExceptionHandler.cs

using System.Net;
using bookapi_minimal.Contracts;
using Microsoft.AspNetCore.Diagnostics;

namespace bookapi_minimal.Exceptions
{

   // Clase manejadora de excepciones globales que implementa IExceptionHandler
    public class GlobalExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<GlobalExceptionHandler> _logger;

        // Constructor para inicializar el registrador
        public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
        {
            _logger = logger;
        }

        // Método para manejar excepciones de forma asíncrona
        public async ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            // Registrar los detalles de la excepción
            _logger.LogError(exception, "An error occurred while processing your request");

            var errorResponse = new ErrorResponse
            {
                Message = exception.Message,
                Title = exception.GetType().Name
            };

            // Determinar el código de estado basado en el tipo de excepción
            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;
            }

            // Establecer el código de estado de la respuesta
            httpContext.Response.StatusCode = errorResponse.StatusCode;

            // Escribir la respuesta de error en formato JSON
            await httpContext.Response.WriteAsJsonAsync(errorResponse, cancellationToken);

            // Devolver verdadero para indicar que la excepción fue manejada
            return true;
        }
    }
}

Desglosemos el código anterior:

  • Definimos una clase llamada GlobalExceptionHandler que implementa la interfaz IExceptionHandler. La interfaz IExceptionHandler se utiliza para manejar excepciones globalmente en la aplicación.

  • La clase GlobalExceptionHandler contiene un constructor que inicializa la dependencia ILogger<GlobalExceptionHandler>. El ILogger se utiliza para registrar información y errores.

  • El método TryHandleAsync se utiliza para manejar excepciones de forma asíncrona. Este método acepta HttpContext, Exception y CancellationToken como parámetros.

  • Registramos los detalles de la excepción utilizando la dependencia ILogger.

  • Creamos un objeto ErrorResponse para representar la respuesta de error devuelta por la API. El objeto ErrorResponse contiene el mensaje de error, título y código de estado.

  • Determinamos el código de estado basado en el tipo de excepción. Si la excepción es una BadHttpRequestException, establecemos el código de estado en BadRequest. Si la excepción es una NoBookFoundException o BookDoesNotExistException, establecemos el código de estado en NotFound. De lo contrario, establecemos el código de estado en InternalServerError.

  • Establecemos el código de estado de la respuesta utilizando la propiedad httpContext.Response.StatusCode.

  • Escribimos la respuesta de error como JSON utilizando el método httpContext.Response.WriteAsJsonAsync.

  • Retornamos true para indicar que la excepción fue manejada con éxito.

Ahora que hemos creado las clases de excepción, registremos el GlobalExceptionHandler en el contenedor de inyección de dependencias. Dado que creamos un método de extensión para registrar servicios en el contenedor de inyección de dependencias, agregaremos el GlobalExceptionHandler a la clase ServiceExtensions.

Actualice la clase ServiceExtensions en la carpeta Extensions de la siguiente manera:


// Extensions/ServiceExtensions.cs
//...
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

builder.Services.AddProblemDetails();

//...

El método AddExceptionHandler registra el GlobalExceptionHandler en el contenedor de inyección de dependencias. El método AddProblemDetails registra la clase ProblemDetails en el contenedor de inyección de dependencias.

Ahora que hemos registrado el GlobalExceptionHandler en el contenedor de inyección de dependencias, podemos usarlo para manejar excepciones globalmente en nuestra aplicación. En la siguiente sección, crearemos los puntos de conexión de la API para interactuar con los datos del libro.

Cómo Crear los Puntos de Conexión de la API

En el contexto de las API mínimas en ASP.NET Core, hay muchas formas de configurar tus puntos de conexión.

Puedes definirlos directamente en tu archivo Program.cs. Pero a medida que tu proyecto crece y necesitas agregar más puntos de conexión o funcionalidad, es útil organizar mejor tu código. Una forma de lograr esto es creando una clase separada para manejar todos los puntos de conexión.

Como hemos discutido anteriormente, las API mínimas no utilizan controladores o vistas como las aplicaciones tradicionales de ASP.NET Core. En su lugar, usan métodos como MapGet, MapPost, MapPut y MapDelete para definir métodos HTTP y rutas para los puntos de conexión de la API.

Para comenzar, navega a la carpeta Endpoints y crea un nuevo archivo llamado BookEndpoints.cs. Añade el siguiente código al archivo:


// Endpoints/BookEndpoints.cs



namespace bookapi_minimal.Endpoints
{
     public static class BookEndPoint
    {
        public static IEndpointRouteBuilder MapBookEndPoint(this IEndpointRouteBuilder app)
        {


            return app;
        }
    }
}

La clase BookEndpoints contiene un método MapBookEndPoint que devuelve un objeto IEndpointRouteBuilder. El objeto IEndpointRouteBuilder se utiliza para definir los métodos HTTP y las rutas para los puntos de conexión de la API. En las siguientes secciones, definiremos los puntos de conexión de la API para crear, leer, actualizar y eliminar libros.

Cómo Crear el Punto de Conexión AddBookAsync para Libros

En esta sección, crearemos el punto de conexión AddBookAsync. Este punto de conexión aceptará un objeto Book como carga útil JSON y lo agregará a la base de datos. Utilizaremos el método MapPost para definir el método HTTP y la ruta para este punto de conexión.

Agrega el siguiente código a la clase BookEndpoints:


// Endpoints/BookEndpoints.cs


//...
   // Punto de conexión para agregar un nuevo libro
      app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
        {
        var result = await bookService.AddBookAsync(createBookRequest);
        return Results.Created($"/books/{result.Id}", result); 
        });


//...
  • Definición de Ruta: El método MapPost define la ruta para el punto de conexión como /books.

  • Modelo de Solicitud: El punto de conexión acepta un objeto CreateBookRequest como carga útil JSON. El objeto CreateBookRequest contiene los datos requeridos para crear un nuevo libro.

  • Modelo de Respuesta: El endpoint devuelve un objeto Book como carga útil en JSON. El objeto Book contiene los datos del libro recién creado.

  • Valor de Retorno: El endpoint devuelve un resultado Created. El resultado Created contiene la ubicación del libro recién creado y el objeto Book.

Cómo Crear el Endpoint de Libro GetBookAsync

En esta sección, crearemos el endpoint GetBookAsync. Este endpoint aceptará un ID de libro como parámetro de consulta y devolverá el libro con el ID especificado. Utilizaremos el método MapGet para definir el método HTTP y la ruta para este endpoint.

Agrega el siguiente código a la clase BookEndpoints:


// Endpoints/BookEndpoints.cs

// ...
    // Endpoint para obtener todos los libros
    app.MapGet("/books", async (IBookService bookService) =>
     {
    var result = await bookService.GetBooksAsync();
    return Results.Ok(result);
});


//...
  • Definición de Ruta: El método MapGet define la ruta para el endpoint como /books.

  • Modelo de Solicitud: El endpoint acepta un objeto Book como carga útil JSON. El objeto Book contiene los datos requeridos para crear un nuevo libro.

  • Modelo de Respuesta: El endpoint devuelve un objeto Book como carga útil JSON. El objeto Book contiene los datos del libro recién creado.

  • Valor de Retorno: El endpoint devuelve un resultado Ok. El resultado Ok contiene el objeto Book.

Cómo Crear el Endpoint de Libro GetBookByIdAsync

En esta sección, crearemos el endpoint GetBookByIdAsync. Este endpoint aceptará un ID de libro como parámetro de ruta y devolverá el libro con el ID especificado. Utilizaremos el método MapGet para definir el método HTTP y la ruta para este endpoint.

Agrega el siguiente código a la clase BookEndpoints:


// Endpoints/BookEndpoints.cs
//...
// Endpoint para obtener un libro 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();
});

//...
  • Definición de Ruta: El método MapGet define la ruta para el endpoint como /books/{id:guid}. El parámetro {id:guid} especifica que el parámetro id debe ser un GUID.

  • Modelo de Solicitud: El endpoint acepta un objeto Book como carga útil JSON. El objeto Book contiene los datos requeridos para crear un nuevo libro.

  • Modelo de Respuesta: El endpoint devuelve un objeto Book como carga útil JSON. El objeto Book contiene los datos del libro recién creado.

  • Valor de Retorno: El endpoint devuelve un resultado Ok si se encuentra el libro. El resultado NotFound se devuelve si el libro no se encuentra.

Cómo Crear el Endpoint de UpdateBookAsync

En esta sección, crearemos el endpoint UpdateBookAsync. Este endpoint aceptará un ID de libro como parámetro de ruta y un objeto Book como carga útil en JSON y actualizará el libro con el ID especificado. Usaremos el método MapPut para definir el método HTTP y la ruta para este endpoint.

Agrega el siguiente código a la clase BookEndpoints:


// Endpoints/BookEndpoints.cs

//...
   // Endpoint para actualizar un libro 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();
});

//...
  • Definición de Ruta: El método MapPut define la ruta para el endpoint como /books/{id:guid}. El parámetro {id:guid} especifica que el parámetro id debe ser un GUID.

  • Modelo de Solicitud: El endpoint acepta un objeto Book como carga útil en JSON. El objeto Book contiene los datos requeridos para crear un nuevo libro.

  • Modelo de Respuesta: El endpoint devuelve un objeto Book como carga útil en JSON. El objeto Book contiene los datos del libro recién creado.

  • Valor de Retorno: El endpoint devuelve un resultado Ok si se encuentra el libro. El resultado NotFound se devuelve si el libro no se encuentra.

Cómo Crear el Endpoint DeleteBookAsync para Libros

En esta sección, crearemos el endpoint DeleteBookAsync. Este endpoint aceptará un ID de libro como parámetro de ruta y eliminará el libro con el ID especificado. Utilizaremos el método MapDelete para definir el método HTTP y la ruta para este endpoint.

Agrega el siguiente código a la clase BookEndpoints:


// Endpoints/BookEndpoints.cs

//...
   // Endpoint para eliminar un libro por ID
 app.MapDelete("/books/{id:guid}", async (Guid id, IBookService bookService) =>
{
var result = await bookService.DeleteBookAsync(id);
   return result ? Results.NoContent() : Results.NotFound();
});


//...
  • Definición de Ruta: El método MapDelete define la ruta para el endpoint como /books/{id:guid}. El parámetro {id:guid} especifica que el parámetro id debe ser un GUID.

  • Modelo de Solicitud: El endpoint acepta un objeto Book como carga útil JSON. El objeto Book contiene los datos requeridos para crear un nuevo libro.

  • Modelo de Respuesta: El endpoint devuelve un objeto Book como carga útil JSON. El objeto Book contiene los datos del libro recién creado.

  • Valor de Retorno: El endpoint devuelve un resultado NoContent si el libro se elimina con éxito. El resultado NotFound se devuelve si el libro no se encuentra.

Ahora hemos definido todos los métodos para los endpoints del libro. Por lo tanto, tu clase de endpoint debería verse así:

// 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)
        {
            // Definir los endpoints

            // Endpoint para agregar un nuevo libro
            app.MapPost("/books", async (CreateBookRequest createBookRequest, IBookService bookService) =>
            {
                var result = await bookService.AddBookAsync(createBookRequest);
                return Results.Created($"/books/{result.Id}", result); 
            });


               // Endpoint para obtener todos los libros
            app.MapGet("/books", async (IBookService bookService) =>
            {
                var result = await bookService.GetBooksAsync();
                return Results.Ok(result);
            });

            // Endpoint para obtener un libro 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 actualizar un libro 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 eliminar un libro 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;
        }
    }
}

¡Felicidades! Has creado todos los endpoints para la API del libro. Los endpoints manejan las operaciones CRUD para los libros y devuelven las respuestas apropiadas basadas en la solicitud y los datos.

Cómo Registrar los Endpoints

Después de definir los puntos de conexión de la API del libro, el siguiente paso es registrar estos puntos de conexión en el archivo Program.cs. Utilizaremos el método MapBookEndpoints para registrar los puntos de conexión del libro.

También deberíamos limpiar nuestra clase Program.cs para asegurarnos de que permanezca organizada y mantenible.

// 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" });


    // Establecer la ruta de comentarios para el JSON y la UI de Swagger.
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath);

});
var app = builder.Build();

// Configurar el pipeline de solicitudes HTTP.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseExceptionHandler();


app.MapGroup("/api/v1/")
   .WithTags(" Book endpoints")
   .MapBookEndPoint();

app.Run();

Desglosemos los componentes clave del archivo Program.cs:

  • AddApplicationServices: Este método registra los servicios necesarios para la API. Es un método de extensión que creamos anteriormente para agregar servicios al contenedor de inyección de dependencias.

  • AddSwaggerGen: Este método registra el generador de Swagger, que se utiliza para crear la documentación de Swagger para la API. Especificamos el título, la versión y la descripción de la API en el documento de Swagger.

  • MapGroup: Este método agrupa los puntos finales. Toma una ruta como parámetro y devuelve un objeto IEndpointRouteBuilder. Utilizamos el método WithTags para agregar etiquetas a los puntos finales y el método MapBookEndpoints para registrar los puntos finales del libro.

  • Run: Este método inicia la aplicación.

Para habilitar la documentación de Swagger, necesitas agregar la propiedad GenerateDocumentationFile a tu archivo .csproj. En este ejemplo, el archivo se llama bookapi-minimal.csproj, pero el nombre puede variar según tu proyecto.

Agrega la siguiente línea a tu archivo .csproj:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Al final, bookapi-minimal.csproj debería verse así:


<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>

Ahora que hemos registrado los puntos finales del libro en el archivo Program.cs, podemos ejecutar la aplicación y probar los puntos finales de la API utilizando Swagger.

Al ejecutar la aplicación, deberías ver la documentación de Swagger en la siguiente URL: https://localhost:5001/swagger/index.html. La documentación de Swagger proporciona información sobre los puntos de conexión de la API, los modelos de solicitud y respuesta, y te permite probar los puntos de conexión directamente desde el navegador. Deberías ver algo como esto:

¡Felicidades! Has implementado la lógica de negocio para el servicio de libros, creado excepciones personalizadas, definido puntos de conexión de la API y registrado los puntos de conexión en el archivo Program.cs. También has habilitado la documentación de Swagger para probar los puntos de conexión de la API.

Cómo Agregar Datos de Semilla a la Base de Datos

Un paso más importante es sembrar la base de datos con datos iniciales cuando la aplicación se inicia. Estos datos de semilla poblarán la base de datos, permitiéndote probar tus puntos de conexión de la API sin agregar datos manualmente.

Vamos a agregar algunos datos de semilla antes de realizar migraciones y probar nuestros puntos de conexión de la API.

Para lograr esto, crearemos una nueva clase en nuestra carpeta de Configuración llamada BookTypeConfigurations y agregaremos el siguiente 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 el nombre de la tabla
            builder.ToTable("Books");

            // Configurar la clave primaria
            builder.HasKey(x => x.Id);

            // Configurar propiedades
            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();

            // Datos de semilla
            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 a desglosar el código anterior:

En Entity Framework Core, puedes usar la interfaz IEntityTypeConfiguration para configurar el tipo de entidad y los datos de semilla para la base de datos. La clase BookTypeConfigurations implementa la interfaz IEntityTypeConfiguration<BookModel> y proporciona la configuración para la entidad BookModel.

  • Método Configure: Este método se utiliza para configurar el tipo de entidad BookModel. Define el nombre de la tabla, la clave primaria y las propiedades para la entidad BookModel.

    • Nombre de la Tabla: El método ToTable especifica el nombre de la tabla que se creará en la base de datos. En este caso, el nombre de la tabla se establece en “Books”.

    • Clave Primaria: El método HasKey especifica la clave primaria para la entidad BookModel. La clave primaria se establece en la propiedad Id.

    • Propiedades: El método Property configura las propiedades de la entidad BookModel. Especifica el tipo de datos, la longitud y las restricciones para cada propiedad.

  • Datos de Semilla: El método HasData siembra la base de datos con datos iniciales. Crea tres objetos BookModel con datos de muestra para probar los puntos de conexión de la API.

Ahora que hemos creado la clase BookTypeConfigurations, necesitamos registrar esta configuración en la clase ApplicationContext. Esto asegura que la configuración se aplique cuando se crea o migra la base de datos.

Finalmente estamos casi listos para probar nuestra API. Pero antes de hacerlo, necesitamos realizar migraciones para crear la base de datos y aplicar los datos de semilla.

¿Recuerdan que agregamos nuestra cadena de conexión a la base de datos en el archivo appsettings.json? Ahora vamos a realizar una migración y luego actualizaremos nuestra base de datos para que la migración surta efecto.

Cómo Realizar una Migración

Las migraciones permiten actualizar el esquema de la base de datos en función de los cambios realizados en las clases de modelo. En Entity Framework Core, puedes usar el comando dotnet ef migrations add para crear una nueva migración que refleje estos cambios.

Para realizar una migración, ejecuta el siguiente comando en la terminal:

dotnet ef migrations add InitialCreate

Si el comando es exitoso, deberías ver una salida similar a esta:

Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

Ahora verás una nueva carpeta llamada Migrations en tu proyecto. Esta carpeta contiene los archivos de migración que se crearon en función de los cambios realizados en tus clases de modelo. Estos archivos de migración incluyen los comandos SQL necesarios para actualizar el esquema de la base de datos.

Cómo Actualizar la Base de Datos

Después de crear la migración, necesitas aplicar la migración para actualizar el esquema de la base de datos. Puedes usar el comando dotnet ef database update para aplicar la migración y actualizar la base de datos. Asegúrate de que el SQL Server esté en ejecución.

Ejecuta el siguiente comando en la terminal:


dotnet ef database update

Esto actualizará el esquema de la base de datos en función de los cambios realizados en tus clases de modelo. Asegúrate de que no haya errores en tu cadena de conexión a la base de datos.

Cómo Probar los Endpoints de la API

Ahora podemos probar nuestros endpoints usando Swagger. Para hacerlo, ejecuta la aplicación mediante el siguiente comando en la terminal:


dotnet run

Esto ejecutará nuestra aplicación. Puedes abrir tu navegador y navegar a https://localhost:5001/swagger/index.html para acceder a la documentación de Swagger. Deberías ver una lista de endpoints de API, modelos de solicitud y respuesta, y la capacidad de probar los endpoints directamente desde el navegador.

Si tu número de puerto es diferente de 5001, no te preocupes – seguirá funcionando. El puerto puede cambiar dependiendo del tipo de máquina que estés usando, pero seguirá logrando el mismo resultado.

Cómo Probar el Endpoint de Obtener Todos los Libros

Para probar el endpoint de Obtener Todos los Libros, sigue estos pasos:

  1. En la documentación de Swagger, haz clic en el endpoint GET /api/v1/books.

  2. Haz clic en el botón Probar.

  3. Haz clic en el botón Ejecutar.

Esto enviará una solicitud a la API para recuperar todos los libros en la base de datos.

Deberías ver la respuesta de la API, que incluirá la lista de libros que fueron sembrados en la base de datos.

La imagen a continuación muestra la respuesta de la API:

Cómo Probar el Endpoint Obtener Libro por ID

Para probar el endpoint Obtener Libro por ID, sigue estos pasos:

  1. En la documentación de Swagger, haz clic en el endpoint GET /api/v1/books/{id}.

  2. Ingresa el ID de un libro en el campo id. Puedes usar uno de los IDs de libros que se sembraron en la base de datos.

  3. Haz clic en el botón Probar.

Esto enviará una solicitud a la API para recuperar el libro con el ID especificado. Deberías ver la respuesta de la API, que incluirá el libro con el ID especificado.

La imagen a continuación muestra la respuesta de la API:

Cómo Probar el Endpoint Agregar Libro

Para probar el endpoint Agregar Libro, sigue estos pasos:

  1. En la documentación de Swagger, haz clic en el endpoint POST /api/v1/books.

  2. Haz clic en el botón Probar.

  3. Ingresa los detalles del libro en el cuerpo de la solicitud.

  4. Haga clic en el botón Ejecutar.

Esto enviará una solicitud a la API para agregar un nuevo libro a la base de datos.

Debería ver la respuesta de la API, que incluirá el libro recién creado.

La imagen a continuación muestra la respuesta de la API:

Cómo Probar el Endpoint Actualizar Libro

Para probar el endpoint Actualizar Libro, siga estos pasos:

  1. En la documentación de Swagger, haga clic en el endpoint PUT /api/v1/books/{id}.

  2. Ingrese el ID de un libro en el campo id. Puede usar el id de uno de los libros que acabamos de agregar.

  3. Haga clic en el botón Probar.

Esto enviará una solicitud a la API para actualizar el libro con el ID especificado.

Debería ver la respuesta de la API, que incluirá el libro actualizado.

La imagen a continuación muestra la respuesta de la API:

Cómo Probar el Endpoint Eliminar Libro

Para probar el endpoint Eliminar Libro, siga estos pasos:

  1. En la documentación de Swagger, haga clic en el endpoint DELETE /api/v1/books/{id}.

  2. Ingrese el ID de un libro en el campo id. Puede usar cualquiera de los ids de los libros que acabamos de agregar o los datos predefinidos.

  3. Haga clic en el botón Probar.

Esto enviará una solicitud a la API para eliminar el libro con el ID especificado.

La imagen a continuación muestra la respuesta de la API:

¡Felicidades! Ha implementado todas las operaciones CRUD para libros y probado los endpoints de la API usando Swagger, verificando que funcionen como se espera. Ahora puede construir sobre esta base para agregar más características y funcionalidades a su API.

Conclusión

Este manual exploró cómo crear una API mínima en ASP.NET Core con .NET 8. Construimos una API de libros completa que soporta operaciones CRUD, implementamos excepciones personalizadas, definimos y registramos endpoints de la API, y habilitamos la documentación de Swagger para facilitar las pruebas.

Al seguir este tutorial, ha obtenido una base sólida para construir APIs mínimas con ASP.NET Core. Ahora puede aplicar este conocimiento y crear APIs robustas para diversos dominios e industrias.

Espero que haya encontrado este tutorial útil e informativo. ¡Gracias por leer!

No dude en conectarse conmigo en las redes sociales: