Creación de una API Web con ASP.NET Core y MongoDB

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

Por Pratik Khandelwal y Scott Addie

En este tutorial se crea una API web que ejecuta operaciones de creación, lectura, actualización y eliminación (CRUD) en una base de datos NoSQL de MongoDB.

En este tutorial aprenderá a:

  • Configurar MongoDB
  • Crear una base de datos de MongoDB
  • Definir un esquema y una colección de MongoDB
  • Realizar operaciones de CRUD de MongoDB desde una API web
  • Personalizar la serialización de JSON

Requisitos previos

Configurar MongoDB

Habilite el acceso a MongoDB y al shell de MongoDB desde cualquier lugar en el equipo de desarrollo:

  1. En Windows, MongoDB está instalado en C:\Archivos de programa\MongoDB de forma predeterminada. Agregue C:\Archivos de programa\MongoDB\Server<número_versión>\bin a la variable de entorno PATH.

  2. Descargue el shell de MongoDB y elija un directorio para extraerlo. Agregue la ruta de acceso resultante para mongosh.exe a la variable de entorno PATH.

  3. Elija un directorio en el equipo de desarrollo para almacenar los datos. Por ejemplo, C:\BooksData en Windows. Si no existe el directorio, créelo. El shell de mongo no crea nuevos directorios.

  4. En el shell de comandos del sistema operativo (no en el shell de MongoDB), use el siguiente comando para conectarse a MongoDB en el puerto predeterminado 27017. Reemplace <data_directory_path> por el directorio elegido en el paso anterior.

    mongod --dbpath <data_directory_path>
    

Use el shell de MongoDB instalado previamente en los pasos siguientes para crear una base de datos, hacer colecciones y almacenar documentos. Para más información sobre los comandos de shell de MongoDB, vea mongosh.

  1. Abra una instancia del shell de comandos de MongoDB; para ello, inicie mongosh.exe.

  2. En el shell de comandos, conéctese a la base de datos de prueba predeterminada ejecutando el comando siguiente:

    mongosh
    
  3. Ejecute el comando siguiente en el shell de comandos:

    use BookStore
    

    Se crea una base de datos denominada BookStore si aún no existe. Si la base de datos existe, su conexión se abre para las transacciones.

  4. Cree una colección Books con el comando siguiente:

    db.createCollection('Books')
    

    Se muestra el siguiente resultado:

    { "ok" : 1 }
    
  5. Defina un esquema para la colección Books e inserte dos documentos con el comando siguiente:

    db.Books.insertMany([{ "Name": "Design Patterns", "Price": 54.93, "Category": "Computers", "Author": "Ralph Johnson" }, { "Name": "Clean Code", "Price": 43.15, "Category": "Computers","Author": "Robert C. Martin" }])
    

    Se muestra un resultado similar al siguiente:

    {
        "acknowledged" : true,
        "insertedIds" : [
            ObjectId("61a6058e6c43f32854e51f51"),
            ObjectId("61a6058e6c43f32854e51f52")
         ]
     }
    

    Nota:

    Los objetos ObjectId que se muestran en el resultado anterior no coincidirán con los que se muestran en el shell de comandos.

  6. Vea los documentos en la base de datos mediante el comando siguiente:

    db.Books.find().pretty()
    

    Se muestra un resultado similar al siguiente:

    {
         "_id" : ObjectId("61a6058e6c43f32854e51f51"),
         "Name" : "Design Patterns",
         "Price" : 54.93,
         "Category" : "Computers",
         "Author" : "Ralph Johnson"
     }
     {
         "_id" : ObjectId("61a6058e6c43f32854e51f52"),
         "Name" : "Clean Code",
         "Price" : 43.15,
         "Category" : "Computers",
         "Author" : "Robert C. Martin"
     }
    

    El esquema agrega una propiedad _id generada automáticamente del tipo ObjectId para cada documento.

Creación de un proyecto de API web de ASP.NET Core

  1. Vaya a Archivo>Nuevo>Proyecto.

  2. Seleccione el tipo de proyecto API web de ASP.NET Core y, luego, Siguiente.

  3. Denomine el proyecto BookStoreApi y seleccione Siguiente.

  4. Seleccione el marco .NET 8.0 (compatibilidad a largo plazo) y, a continuación, Crear.

  5. En la ventana Consola del Administrador de paquetes, desplácese hasta la raíz del proyecto. Ejecute el siguiente comando para instalar el controlador .NET para MongoDB:

    Install-Package MongoDB.Driver
    

Adición de un modelo de entidad

  1. Agregue un directorio Modelos a la raíz del proyecto.

  2. Agregue una clase Book al directorio Book con el código siguiente:

    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace BookStoreApi.Models;
    
    public class Book
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string? Id { get; set; }
    
        [BsonElement("Name")]
        public string BookName { get; set; } = null!;
    
        public decimal Price { get; set; }
    
        public string Category { get; set; } = null!;
    
        public string Author { get; set; } = null!;
    }
    

    En la clase anterior, se requiere la propiedad Id:

    La propiedad BookName se anota con el atributo [BsonElement]. El valor Name del atributo representa el nombre de propiedad en la colección de MongoDB.

Adición de un modelo configuración

  1. Agregue los siguientes valores de configuración de base de datos a appsettings.json :

    {
        "BookStoreDatabase": {
            "ConnectionString": "mongodb://localhost:27017",
            "DatabaseName": "BookStore",
            "BooksCollectionName": "Books"
        },
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.AspNetCore": "Warning"
            }
        },
        "AllowedHosts": "*"
    }
    
  2. Agregue una clase BookStoreDatabaseSettings al directorio BookStoreDatabaseSettings con el código siguiente:

    namespace BookStoreApi.Models;
    
    public class BookStoreDatabaseSettings
    {
        public string ConnectionString { get; set; } = null!;
    
        public string DatabaseName { get; set; } = null!;
    
        public string BooksCollectionName { get; set; } = null!;
    }
    

    La clase anterior BookStoreDatabaseSettings se utiliza para almacenar los valores de propiedad BookStoreDatabase del archivo appsettings.json . Los nombres de las propiedades de JSON y C# son iguales para facilitar el proceso de asignación.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    

    En el código anterior, la instancia de configuración a la que la sección BookStoreDatabase del archivo appsettings.json enlaza está registrada en el contenedor de inserción de dependencias (DI). Por ejemplo, una propiedad ConnectionString del objeto BookStoreDatabaseSettings se rellena con la propiedad BookStoreDatabase:ConnectionString en appsettings.json.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BookStoreDatabaseSettings:

    using BookStoreApi.Models;
    

Adición de un servicio de operaciones CRUD

  1. Agregue un directorio Servicios a la raíz del proyecto.

  2. Agregue una clase BooksService al directorio BooksService con el código siguiente:

    using BookStoreApi.Models;
    using Microsoft.Extensions.Options;
    using MongoDB.Driver;
    
    namespace BookStoreApi.Services;
    
    public class BooksService
    {
        private readonly IMongoCollection<Book> _booksCollection;
    
        public BooksService(
            IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
        {
            var mongoClient = new MongoClient(
                bookStoreDatabaseSettings.Value.ConnectionString);
    
            var mongoDatabase = mongoClient.GetDatabase(
                bookStoreDatabaseSettings.Value.DatabaseName);
    
            _booksCollection = mongoDatabase.GetCollection<Book>(
                bookStoreDatabaseSettings.Value.BooksCollectionName);
        }
    
        public async Task<List<Book>> GetAsync() =>
            await _booksCollection.Find(_ => true).ToListAsync();
    
        public async Task<Book?> GetAsync(string id) =>
            await _booksCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
    
        public async Task CreateAsync(Book newBook) =>
            await _booksCollection.InsertOneAsync(newBook);
    
        public async Task UpdateAsync(string id, Book updatedBook) =>
            await _booksCollection.ReplaceOneAsync(x => x.Id == id, updatedBook);
    
        public async Task RemoveAsync(string id) =>
            await _booksCollection.DeleteOneAsync(x => x.Id == id);
    }
    

    En el código anterior, se recuperó una instancia de BookStoreDatabaseSettings de la inserción de dependencias mediante la inserción de un constructor. Esta técnica proporciona acceso a los valores de configuración de appsettings.json que se agregaron en la sección appsettings.json.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    

    En el código anterior, la clase BooksService se registra con inserción de dependencias para admitir la inserción del constructor en las clases de consumo. La duración de servicio de tipo singleton es la más adecuada porque BooksService toma una dependencia directa sobre MongoClient. Según las instrucciones oficiales de reutilización de cliente Mongo, MongoClient debe registrarse en la inserción de dependencias con una duración de servicio de tipo singleton.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BooksService:

    using BookStoreApi.Services;
    

La clase BooksService usa los miembros MongoDB.Driver siguientes para ejecutar operaciones CRUD en la base de datos:

  • MongoClient: lee la instancia del servidor para ejecutar operaciones de base de datos. Se proporciona la cadena de conexión de MongoDB al constructor de esta clase:

    public BooksService(
        IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
    {
        var mongoClient = new MongoClient(
            bookStoreDatabaseSettings.Value.ConnectionString);
    
        var mongoDatabase = mongoClient.GetDatabase(
            bookStoreDatabaseSettings.Value.DatabaseName);
    
        _booksCollection = mongoDatabase.GetCollection<Book>(
            bookStoreDatabaseSettings.Value.BooksCollectionName);
    }
    
  • IMongoDatabase: representa la base de datos de Mongo para ejecutar operaciones. Este tutorial usa el método genérico GetCollection<TDocument>(collection) en la interfaz para tener acceso a los datos de una colección específica. Ejecute las operaciones CRUD en la colección después de llamar a este método. En la llamada al método GetCollection<TDocument>(collection):

    • collection representa el nombre de la colección.
    • TDocument representa el tipo de objeto CLR almacenado en la colección.

GetCollection<TDocument>(collection) devuelve un objeto GetCollection<TDocument>(collection) que representa la colección. En este tutorial, se invocan los métodos siguientes en la colección:

  • DeleteOneAsync: elimina un único documento que cumpla los criterios de búsqueda proporcionados.
  • Find<TDocument>: devuelve todos los documentos de la colección que cumplen los criterios de búsqueda indicados.
  • InsertOneAsync: inserta el objeto proporcionado como un nuevo documento en la colección.
  • ReplaceOneAsync: reemplaza un único documento que cumpla los criterios de búsqueda indicados por el objeto proporcionado.

Incorporación de un controlador

Agregue una clase BooksController al directorio BooksController con el código siguiente:

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
    private readonly BooksService _booksService;

    public BooksController(BooksService booksService) =>
        _booksService = booksService;

    [HttpGet]
    public async Task<List<Book>> Get() =>
        await _booksService.GetAsync();

    [HttpGet("{id:length(24)}")]
    public async Task<ActionResult<Book>> Get(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        return book;
    }

    [HttpPost]
    public async Task<IActionResult> Post(Book newBook)
    {
        await _booksService.CreateAsync(newBook);

        return CreatedAtAction(nameof(Get), new { id = newBook.Id }, newBook);
    }

    [HttpPut("{id:length(24)}")]
    public async Task<IActionResult> Update(string id, Book updatedBook)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        updatedBook.Id = book.Id;

        await _booksService.UpdateAsync(id, updatedBook);

        return NoContent();
    }

    [HttpDelete("{id:length(24)}")]
    public async Task<IActionResult> Delete(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        await _booksService.RemoveAsync(id);

        return NoContent();
    }
}

El controlador de API web anterior:

  • Usa la clase BooksService para ejecutar operaciones CRUD.
  • Contiene métodos de acción para admitir las solicitudes GET, POST, PUT y DELETE de HTTP.
  • Llama a CreatedAtAction en el método de acción Create para devolver una respuesta CreatedAtAction. El código de estado 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor. CreatedAtAction también agrega un encabezado Location a la respuesta. El encabezado Location especifica el identificador URI del libro recién creado.

Prueba de la API web

  1. Compile y ejecute la aplicación.

  2. Vaya a https://localhost:<port>/api/books, donde <port> es el número de puerto asignado automáticamente para la aplicación, para probar el método de acción Get sin parámetros del controlador. Se muestra una respuesta JSON similar a la siguiente:

    [
      {
        "id": "61a6058e6c43f32854e51f51",
        "bookName": "Design Patterns",
        "price": 54.93,
        "category": "Computers",
        "author": "Ralph Johnson"
      },
      {
        "id": "61a6058e6c43f32854e51f52",
        "bookName": "Clean Code",
        "price": 43.15,
        "category": "Computers",
        "author": "Robert C. Martin"
      }
    ]
    
  3. Vaya a https://localhost:<port>/api/books/{id here} para probar el método de acción Get sobrecargado del controlador. Se muestra una respuesta JSON similar a la siguiente:

    {
      "id": "61a6058e6c43f32854e51f52",
      "bookName": "Clean Code",
      "price": 43.15,
      "category": "Computers",
      "author": "Robert C. Martin"
    }
    

Configuración de las opciones de serialización de JSON

Hay dos detalles que cambiar sobre las respuestas JSON devueltas en la sección Prueba de la API web:

  • Las mayúsculas y minúsculas Camel predeterminadas de los nombres de propiedad se deben cambiar para que coincidan con el uso de mayúsculas y minúsculas de Pascal de los nombres de propiedad del objeto CLR.
  • La propiedad bookName se debe devolver como Name.

Para satisfacer los requisitos anteriores, realice los cambios siguientes:

  1. En Program.cs, cambie el código resaltado siguiente en la llamada al método AddControllers:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    
    builder.Services.AddControllers()
        .AddJsonOptions(
            options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
    

    Con el cambio anterior, los nombres de propiedad de la respuesta JSON serializada de la API web coinciden con sus nombres de propiedad correspondientes en el tipo de objeto CLR. Por ejemplo, la propiedad Author de la clase Book se serializa como Author, en lugar de author.

  2. En Models/Book.cs, anote la propiedad BookName con el atributo [JsonPropertyName]:

    [BsonElement("Name")]
    [JsonPropertyName("Name")]
    public string BookName { get; set; } = null!;
    

    El valor Name del atributo [JsonPropertyName] representa el nombre de propiedad en la respuesta JSON serializada de la API web.

  3. Agregue el código siguiente en la parte superior de Models/Book.cs para resolver la referencia al atributo [JsonProperty]:

    using System.Text.Json.Serialization;
    
  4. Repita los pasos definidos en la sección Prueba de la API web. Observe la diferencia en los nombres de propiedad JSON.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Recursos adicionales

En este tutorial se crea una API web que ejecuta operaciones de creación, lectura, actualización y eliminación (CRUD) en una base de datos NoSQL de MongoDB.

En este tutorial aprenderá a:

  • Configurar MongoDB
  • Crear una base de datos de MongoDB
  • Definir un esquema y una colección de MongoDB
  • Realizar operaciones de CRUD de MongoDB desde una API web
  • Personalizar la serialización de JSON

Requisitos previos

Configurar MongoDB

Habilite el acceso a MongoDB y al shell de MongoDB desde cualquier lugar en el equipo de desarrollo:

  1. En Windows, MongoDB está instalado en C:\Archivos de programa\MongoDB de forma predeterminada. Agregue C:\Archivos de programa\MongoDB\Server<número_versión>\bin a la variable de entorno PATH.

  2. Descargue el shell de MongoDB y elija un directorio para extraerlo. Agregue la ruta de acceso resultante para mongosh.exe a la variable de entorno PATH.

  3. Elija un directorio en el equipo de desarrollo para almacenar los datos. Por ejemplo, C:\BooksData en Windows. Si no existe el directorio, créelo. El shell de mongo no crea nuevos directorios.

  4. En el shell de comandos del sistema operativo (no en el shell de MongoDB), use el siguiente comando para conectarse a MongoDB en el puerto predeterminado 27017. Reemplace <data_directory_path> por el directorio elegido en el paso anterior.

    mongod --dbpath <data_directory_path>
    

Use el shell de MongoDB instalado previamente en los pasos siguientes para crear una base de datos, hacer colecciones y almacenar documentos. Para más información sobre los comandos de shell de MongoDB, vea mongosh.

  1. Abra una instancia del shell de comandos de MongoDB; para ello, inicie mongosh.exe.

  2. En el shell de comandos, conéctese a la base de datos de prueba predeterminada ejecutando el comando siguiente:

    mongosh
    
  3. Ejecute el comando siguiente en el shell de comandos:

    use BookStore
    

    Se crea una base de datos denominada BookStore si aún no existe. Si la base de datos existe, su conexión se abre para las transacciones.

  4. Cree una colección Books con el comando siguiente:

    db.createCollection('Books')
    

    Se muestra el siguiente resultado:

    { "ok" : 1 }
    
  5. Defina un esquema para la colección Books e inserte dos documentos con el comando siguiente:

    db.Books.insertMany([{ "Name": "Design Patterns", "Price": 54.93, "Category": "Computers", "Author": "Ralph Johnson" }, { "Name": "Clean Code", "Price": 43.15, "Category": "Computers","Author": "Robert C. Martin" }])
    

    Se muestra un resultado similar al siguiente:

    {
        "acknowledged" : true,
        "insertedIds" : [
            ObjectId("61a6058e6c43f32854e51f51"),
            ObjectId("61a6058e6c43f32854e51f52")
         ]
     }
    

    Nota:

    Los objetos ObjectId que se muestran en el resultado anterior no coincidirán con los que se muestran en el shell de comandos.

  6. Vea los documentos en la base de datos mediante el comando siguiente:

    db.Books.find().pretty()
    

    Se muestra un resultado similar al siguiente:

    {
         "_id" : ObjectId("61a6058e6c43f32854e51f51"),
         "Name" : "Design Patterns",
         "Price" : 54.93,
         "Category" : "Computers",
         "Author" : "Ralph Johnson"
     }
     {
         "_id" : ObjectId("61a6058e6c43f32854e51f52"),
         "Name" : "Clean Code",
         "Price" : 43.15,
         "Category" : "Computers",
         "Author" : "Robert C. Martin"
     }
    

    El esquema agrega una propiedad _id generada automáticamente del tipo ObjectId para cada documento.

Creación de un proyecto de API web de ASP.NET Core

  1. Vaya a Archivo>Nuevo>Proyecto.

  2. Seleccione el tipo de proyecto API web de ASP.NET Core y, luego, Siguiente.

  3. Denomine el proyecto BookStoreApi y seleccione Siguiente.

  4. Seleccione el marco .NET 7.0 (compatibilidad a plazo estándar) y, a continuación, Crear.

  5. En el menú Herramientas, seleccione Administrador de paquetes NuGet>Consola del Administrador de paquetes.

  6. En la ventana Consola del Administrador de paquetes, desplácese hasta la raíz del proyecto. Ejecute el siguiente comando para instalar el controlador .NET para MongoDB:

    Install-Package MongoDB.Driver
    

Adición de un modelo de entidad

  1. Agregue un directorio Modelos a la raíz del proyecto.

  2. Agregue una clase Book al directorio Book con el código siguiente:

    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace BookStoreApi.Models;
    
    public class Book
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string? Id { get; set; }
    
        [BsonElement("Name")]
        public string BookName { get; set; } = null!;
    
        public decimal Price { get; set; }
    
        public string Category { get; set; } = null!;
    
        public string Author { get; set; } = null!;
    }
    

    En la clase anterior, se requiere la propiedad Id:

    La propiedad BookName se anota con el atributo [BsonElement]. El valor Name del atributo representa el nombre de propiedad en la colección de MongoDB.

Adición de un modelo configuración

  1. Agregue los siguientes valores de configuración de base de datos a appsettings.json :

    {
      "BookStoreDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookStore",
        "BooksCollectionName": "Books"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    
  2. Agregue una clase BookStoreDatabaseSettings al directorio BookStoreDatabaseSettings con el código siguiente:

    namespace BookStoreApi.Models;
    
    public class BookStoreDatabaseSettings
    {
        public string ConnectionString { get; set; } = null!;
    
        public string DatabaseName { get; set; } = null!;
    
        public string BooksCollectionName { get; set; } = null!;
    }
    

    La clase anterior BookStoreDatabaseSettings se utiliza para almacenar los valores de propiedad BookStoreDatabase del archivo appsettings.json . Los nombres de las propiedades de JSON y C# son iguales para facilitar el proceso de asignación.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    

    En el código anterior, la instancia de configuración a la que la sección BookStoreDatabase del archivo appsettings.json enlaza está registrada en el contenedor de inserción de dependencias (DI). Por ejemplo, una propiedad ConnectionString del objeto BookStoreDatabaseSettings se rellena con la propiedad BookStoreDatabase:ConnectionString en appsettings.json.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BookStoreDatabaseSettings:

    using BookStoreApi.Models;
    

Adición de un servicio de operaciones CRUD

  1. Agregue un directorio Servicios a la raíz del proyecto.

  2. Agregue una clase BooksService al directorio BooksService con el código siguiente:

    using BookStoreApi.Models;
    using Microsoft.Extensions.Options;
    using MongoDB.Driver;
    
    namespace BookStoreApi.Services;
    
    public class BooksService
    {
        private readonly IMongoCollection<Book> _booksCollection;
    
        public BooksService(
            IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
        {
            var mongoClient = new MongoClient(
                bookStoreDatabaseSettings.Value.ConnectionString);
    
            var mongoDatabase = mongoClient.GetDatabase(
                bookStoreDatabaseSettings.Value.DatabaseName);
    
            _booksCollection = mongoDatabase.GetCollection<Book>(
                bookStoreDatabaseSettings.Value.BooksCollectionName);
        }
    
        public async Task<List<Book>> GetAsync() =>
            await _booksCollection.Find(_ => true).ToListAsync();
    
        public async Task<Book?> GetAsync(string id) =>
            await _booksCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
    
        public async Task CreateAsync(Book newBook) =>
            await _booksCollection.InsertOneAsync(newBook);
    
        public async Task UpdateAsync(string id, Book updatedBook) =>
            await _booksCollection.ReplaceOneAsync(x => x.Id == id, updatedBook);
    
        public async Task RemoveAsync(string id) =>
            await _booksCollection.DeleteOneAsync(x => x.Id == id);
    }
    

    En el código anterior, se recuperó una instancia de BookStoreDatabaseSettings de la inserción de dependencias mediante la inserción de un constructor. Esta técnica proporciona acceso a los valores de configuración de appsettings.json que se agregaron en la sección appsettings.json.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    

    En el código anterior, la clase BooksService se registra con inserción de dependencias para admitir la inserción del constructor en las clases de consumo. La duración de servicio de tipo singleton es la más adecuada porque BooksService toma una dependencia directa sobre MongoClient. Según las instrucciones oficiales de reutilización de cliente Mongo, MongoClient debe registrarse en la inserción de dependencias con una duración de servicio de tipo singleton.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BooksService:

    using BookStoreApi.Services;
    

La clase BooksService usa los miembros MongoDB.Driver siguientes para ejecutar operaciones CRUD en la base de datos:

  • MongoClient: lee la instancia del servidor para ejecutar operaciones de base de datos. Se proporciona la cadena de conexión de MongoDB al constructor de esta clase:

    public BooksService(
        IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
    {
        var mongoClient = new MongoClient(
            bookStoreDatabaseSettings.Value.ConnectionString);
    
        var mongoDatabase = mongoClient.GetDatabase(
            bookStoreDatabaseSettings.Value.DatabaseName);
    
        _booksCollection = mongoDatabase.GetCollection<Book>(
            bookStoreDatabaseSettings.Value.BooksCollectionName);
    }
    
  • IMongoDatabase: representa la base de datos de Mongo para ejecutar operaciones. Este tutorial usa el método genérico GetCollection<TDocument>(collection) en la interfaz para tener acceso a los datos de una colección específica. Ejecute las operaciones CRUD en la colección después de llamar a este método. En la llamada al método GetCollection<TDocument>(collection):

    • collection representa el nombre de la colección.
    • TDocument representa el tipo de objeto CLR almacenado en la colección.

GetCollection<TDocument>(collection) devuelve un objeto GetCollection<TDocument>(collection) que representa la colección. En este tutorial, se invocan los métodos siguientes en la colección:

  • DeleteOneAsync: elimina un único documento que cumpla los criterios de búsqueda proporcionados.
  • Find<TDocument>: devuelve todos los documentos de la colección que cumplen los criterios de búsqueda indicados.
  • InsertOneAsync: inserta el objeto proporcionado como un nuevo documento en la colección.
  • ReplaceOneAsync: reemplaza un único documento que cumpla los criterios de búsqueda indicados por el objeto proporcionado.

Incorporación de un controlador

Agregue una clase BooksController al directorio BooksController con el código siguiente:

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
    private readonly BooksService _booksService;

    public BooksController(BooksService booksService) =>
        _booksService = booksService;

    [HttpGet]
    public async Task<List<Book>> Get() =>
        await _booksService.GetAsync();

    [HttpGet("{id:length(24)}")]
    public async Task<ActionResult<Book>> Get(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        return book;
    }

    [HttpPost]
    public async Task<IActionResult> Post(Book newBook)
    {
        await _booksService.CreateAsync(newBook);

        return CreatedAtAction(nameof(Get), new { id = newBook.Id }, newBook);
    }

    [HttpPut("{id:length(24)}")]
    public async Task<IActionResult> Update(string id, Book updatedBook)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        updatedBook.Id = book.Id;

        await _booksService.UpdateAsync(id, updatedBook);

        return NoContent();
    }

    [HttpDelete("{id:length(24)}")]
    public async Task<IActionResult> Delete(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        await _booksService.RemoveAsync(id);

        return NoContent();
    }
}

El controlador de API web anterior:

  • Usa la clase BooksService para ejecutar operaciones CRUD.
  • Contiene métodos de acción para admitir las solicitudes GET, POST, PUT y DELETE de HTTP.
  • Llama a CreatedAtAction en el método de acción Create para devolver una respuesta CreatedAtAction. El código de estado 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor. CreatedAtAction también agrega un encabezado Location a la respuesta. El encabezado Location especifica el identificador URI del libro recién creado.

Prueba de la API web

  1. Compile y ejecute la aplicación.

  2. Vaya a https://localhost:<port>/api/books, donde <port> es el número de puerto asignado automáticamente para la aplicación, para probar el método de acción Get sin parámetros del controlador. Se muestra una respuesta JSON similar a la siguiente:

    [
      {
        "id": "61a6058e6c43f32854e51f51",
        "bookName": "Design Patterns",
        "price": 54.93,
        "category": "Computers",
        "author": "Ralph Johnson"
      },
      {
        "id": "61a6058e6c43f32854e51f52",
        "bookName": "Clean Code",
        "price": 43.15,
        "category": "Computers",
        "author": "Robert C. Martin"
      }
    ]
    
  3. Vaya a https://localhost:<port>/api/books/{id here} para probar el método de acción Get sobrecargado del controlador. Se muestra una respuesta JSON similar a la siguiente:

    {
      "id": "61a6058e6c43f32854e51f52",
      "bookName": "Clean Code",
      "price": 43.15,
      "category": "Computers",
      "author": "Robert C. Martin"
    }
    

Configuración de las opciones de serialización de JSON

Hay dos detalles que cambiar sobre las respuestas JSON devueltas en la sección Prueba de la API web:

  • Las mayúsculas y minúsculas Camel predeterminadas de los nombres de propiedad se deben cambiar para que coincidan con el uso de mayúsculas y minúsculas de Pascal de los nombres de propiedad del objeto CLR.
  • La propiedad bookName se debe devolver como Name.

Para satisfacer los requisitos anteriores, realice los cambios siguientes:

  1. En Program.cs, cambie el código resaltado siguiente en la llamada al método AddControllers:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    
    builder.Services.AddControllers()
        .AddJsonOptions(
            options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
    

    Con el cambio anterior, los nombres de propiedad de la respuesta JSON serializada de la API web coinciden con sus nombres de propiedad correspondientes en el tipo de objeto CLR. Por ejemplo, la propiedad Author de la clase Book se serializa como Author, en lugar de author.

  2. En Models/Book.cs, anote la propiedad BookName con el atributo [JsonPropertyName]:

    [BsonElement("Name")]
    [JsonPropertyName("Name")]
    public string BookName { get; set; } = null!;
    

    El valor Name del atributo [JsonPropertyName] representa el nombre de propiedad en la respuesta JSON serializada de la API web.

  3. Agregue el código siguiente en la parte superior de Models/Book.cs para resolver la referencia al atributo [JsonProperty]:

    using System.Text.Json.Serialization;
    
  4. Repita los pasos definidos en la sección Prueba de la API web. Observe la diferencia en los nombres de propiedad JSON.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Recursos adicionales

En este tutorial se crea una API web que ejecuta operaciones de creación, lectura, actualización y eliminación (CRUD) en una base de datos NoSQL de MongoDB.

En este tutorial aprenderá a:

  • Configurar MongoDB
  • Crear una base de datos de MongoDB
  • Definir un esquema y una colección de MongoDB
  • Realizar operaciones de CRUD de MongoDB desde una API web
  • Personalizar la serialización de JSON

Requisitos previos

Configurar MongoDB

Habilite el acceso a MongoDB y al shell de MongoDB desde cualquier lugar en el equipo de desarrollo:

  1. En Windows, MongoDB está instalado en C:\Archivos de programa\MongoDB de forma predeterminada. Agregue C:\Archivos de programa\MongoDB\Server<número_versión>\bin a la variable de entorno PATH.

  2. Descargue el shell de MongoDB y elija un directorio para extraerlo. Agregue la ruta de acceso resultante para mongosh.exe a la variable de entorno PATH.

  3. Elija un directorio en el equipo de desarrollo para almacenar los datos. Por ejemplo, C:\BooksData en Windows. Si no existe el directorio, créelo. El shell de mongo no crea nuevos directorios.

  4. En el shell de comandos del sistema operativo (no en el shell de MongoDB), use el siguiente comando para conectarse a MongoDB en el puerto predeterminado 27017. Reemplace <data_directory_path> por el directorio elegido en el paso anterior.

    mongod --dbpath <data_directory_path>
    

Use el shell de MongoDB instalado previamente en los pasos siguientes para crear una base de datos, hacer colecciones y almacenar documentos. Para más información sobre los comandos de shell de MongoDB, vea mongosh.

  1. Abra una instancia del shell de comandos de MongoDB; para ello, inicie mongosh.exe.

  2. En el shell de comandos, conéctese a la base de datos de prueba predeterminada ejecutando el comando siguiente:

    mongosh
    
  3. Ejecute el comando siguiente en el shell de comandos:

    use BookStore
    

    Se crea una base de datos denominada BookStore si aún no existe. Si la base de datos existe, su conexión se abre para las transacciones.

  4. Cree una colección Books con el comando siguiente:

    db.createCollection('Books')
    

    Se muestra el siguiente resultado:

    { "ok" : 1 }
    
  5. Defina un esquema para la colección Books e inserte dos documentos con el comando siguiente:

    db.Books.insertMany([{ "Name": "Design Patterns", "Price": 54.93, "Category": "Computers", "Author": "Ralph Johnson" }, { "Name": "Clean Code", "Price": 43.15, "Category": "Computers","Author": "Robert C. Martin" }])
    

    Se muestra un resultado similar al siguiente:

    {
        "acknowledged" : true,
        "insertedIds" : [
            ObjectId("61a6058e6c43f32854e51f51"),
            ObjectId("61a6058e6c43f32854e51f52")
         ]
     }
    

    Nota:

    Los objetos ObjectId que se muestran en el resultado anterior no coincidirán con los que se muestran en el shell de comandos.

  6. Vea los documentos en la base de datos mediante el comando siguiente:

    db.Books.find().pretty()
    

    Se muestra un resultado similar al siguiente:

    {
         "_id" : ObjectId("61a6058e6c43f32854e51f51"),
         "Name" : "Design Patterns",
         "Price" : 54.93,
         "Category" : "Computers",
         "Author" : "Ralph Johnson"
     }
     {
         "_id" : ObjectId("61a6058e6c43f32854e51f52"),
         "Name" : "Clean Code",
         "Price" : 43.15,
         "Category" : "Computers",
         "Author" : "Robert C. Martin"
     }
    

    El esquema agrega una propiedad _id generada automáticamente del tipo ObjectId para cada documento.

Creación de un proyecto de API web de ASP.NET Core

  1. Vaya a Archivo>Nuevo>Proyecto.

  2. Seleccione el tipo de proyecto API web de ASP.NET Core y, luego, Siguiente.

  3. Denomine el proyecto BookStoreApi y seleccione Siguiente.

  4. Seleccione el marco .NET 6.0 (compatibilidad a largo plazo) y, a continuación, Crear.

  5. En la ventana Consola del Administrador de paquetes, desplácese hasta la raíz del proyecto. Ejecute el siguiente comando para instalar el controlador .NET para MongoDB:

    Install-Package MongoDB.Driver
    

Adición de un modelo de entidad

  1. Agregue un directorio Modelos a la raíz del proyecto.

  2. Agregue una clase Book al directorio Book con el código siguiente:

    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace BookStoreApi.Models;
    
    public class Book
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string? Id { get; set; }
    
        [BsonElement("Name")]
        public string BookName { get; set; } = null!;
    
        public decimal Price { get; set; }
    
        public string Category { get; set; } = null!;
    
        public string Author { get; set; } = null!;
    }
    

    En la clase anterior, se requiere la propiedad Id:

    La propiedad BookName se anota con el atributo [BsonElement]. El valor Name del atributo representa el nombre de propiedad en la colección de MongoDB.

Adición de un modelo configuración

  1. Agregue los siguientes valores de configuración de base de datos a appsettings.json :

    {
        "BookStoreDatabase": {
            "ConnectionString": "mongodb://localhost:27017",
            "DatabaseName": "BookStore",
            "BooksCollectionName": "Books"
        },
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.AspNetCore": "Warning"
            }
        },
        "AllowedHosts": "*"
    }
    
  2. Agregue una clase BookStoreDatabaseSettings al directorio BookStoreDatabaseSettings con el código siguiente:

    namespace BookStoreApi.Models;
    
    public class BookStoreDatabaseSettings
    {
        public string ConnectionString { get; set; } = null!;
    
        public string DatabaseName { get; set; } = null!;
    
        public string BooksCollectionName { get; set; } = null!;
    }
    

    La clase anterior BookStoreDatabaseSettings se utiliza para almacenar los valores de propiedad BookStoreDatabase del archivo appsettings.json . Los nombres de las propiedades de JSON y C# son iguales para facilitar el proceso de asignación.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    

    En el código anterior, la instancia de configuración a la que la sección BookStoreDatabase del archivo appsettings.json enlaza está registrada en el contenedor de inserción de dependencias (DI). Por ejemplo, una propiedad ConnectionString del objeto BookStoreDatabaseSettings se rellena con la propiedad BookStoreDatabase:ConnectionString en appsettings.json.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BookStoreDatabaseSettings:

    using BookStoreApi.Models;
    

Adición de un servicio de operaciones CRUD

  1. Agregue un directorio Servicios a la raíz del proyecto.

  2. Agregue una clase BooksService al directorio BooksService con el código siguiente:

    using BookStoreApi.Models;
    using Microsoft.Extensions.Options;
    using MongoDB.Driver;
    
    namespace BookStoreApi.Services;
    
    public class BooksService
    {
        private readonly IMongoCollection<Book> _booksCollection;
    
        public BooksService(
            IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
        {
            var mongoClient = new MongoClient(
                bookStoreDatabaseSettings.Value.ConnectionString);
    
            var mongoDatabase = mongoClient.GetDatabase(
                bookStoreDatabaseSettings.Value.DatabaseName);
    
            _booksCollection = mongoDatabase.GetCollection<Book>(
                bookStoreDatabaseSettings.Value.BooksCollectionName);
        }
    
        public async Task<List<Book>> GetAsync() =>
            await _booksCollection.Find(_ => true).ToListAsync();
    
        public async Task<Book?> GetAsync(string id) =>
            await _booksCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
    
        public async Task CreateAsync(Book newBook) =>
            await _booksCollection.InsertOneAsync(newBook);
    
        public async Task UpdateAsync(string id, Book updatedBook) =>
            await _booksCollection.ReplaceOneAsync(x => x.Id == id, updatedBook);
    
        public async Task RemoveAsync(string id) =>
            await _booksCollection.DeleteOneAsync(x => x.Id == id);
    }
    

    En el código anterior, se recuperó una instancia de BookStoreDatabaseSettings de la inserción de dependencias mediante la inserción de un constructor. Esta técnica proporciona acceso a los valores de configuración de appsettings.json que se agregaron en la sección appsettings.json.

  3. Agregue el código resaltado siguiente a Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    

    En el código anterior, la clase BooksService se registra con inserción de dependencias para admitir la inserción del constructor en las clases de consumo. La duración de servicio de tipo singleton es la más adecuada porque BooksService toma una dependencia directa sobre MongoClient. Según las instrucciones oficiales de reutilización de cliente Mongo, MongoClient debe registrarse en la inserción de dependencias con una duración de servicio de tipo singleton.

  4. Agregue el código siguiente en la parte superior del archivo Program.cs para resolver la referencia a BooksService:

    using BookStoreApi.Services;
    

La clase BooksService usa los miembros MongoDB.Driver siguientes para ejecutar operaciones CRUD en la base de datos:

  • MongoClient: lee la instancia del servidor para ejecutar operaciones de base de datos. Se proporciona la cadena de conexión de MongoDB al constructor de esta clase:

    public BooksService(
        IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
    {
        var mongoClient = new MongoClient(
            bookStoreDatabaseSettings.Value.ConnectionString);
    
        var mongoDatabase = mongoClient.GetDatabase(
            bookStoreDatabaseSettings.Value.DatabaseName);
    
        _booksCollection = mongoDatabase.GetCollection<Book>(
            bookStoreDatabaseSettings.Value.BooksCollectionName);
    }
    
  • IMongoDatabase: representa la base de datos de Mongo para ejecutar operaciones. Este tutorial usa el método genérico GetCollection<TDocument>(collection) en la interfaz para tener acceso a los datos de una colección específica. Ejecute las operaciones CRUD en la colección después de llamar a este método. En la llamada al método GetCollection<TDocument>(collection):

    • collection representa el nombre de la colección.
    • TDocument representa el tipo de objeto CLR almacenado en la colección.

GetCollection<TDocument>(collection) devuelve un objeto GetCollection<TDocument>(collection) que representa la colección. En este tutorial, se invocan los métodos siguientes en la colección:

  • DeleteOneAsync: elimina un único documento que cumpla los criterios de búsqueda proporcionados.
  • Find<TDocument>: devuelve todos los documentos de la colección que cumplen los criterios de búsqueda indicados.
  • InsertOneAsync: inserta el objeto proporcionado como un nuevo documento en la colección.
  • ReplaceOneAsync: reemplaza un único documento que cumpla los criterios de búsqueda indicados por el objeto proporcionado.

Incorporación de un controlador

Agregue una clase BooksController al directorio BooksController con el código siguiente:

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
    private readonly BooksService _booksService;

    public BooksController(BooksService booksService) =>
        _booksService = booksService;

    [HttpGet]
    public async Task<List<Book>> Get() =>
        await _booksService.GetAsync();

    [HttpGet("{id:length(24)}")]
    public async Task<ActionResult<Book>> Get(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        return book;
    }

    [HttpPost]
    public async Task<IActionResult> Post(Book newBook)
    {
        await _booksService.CreateAsync(newBook);

        return CreatedAtAction(nameof(Get), new { id = newBook.Id }, newBook);
    }

    [HttpPut("{id:length(24)}")]
    public async Task<IActionResult> Update(string id, Book updatedBook)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        updatedBook.Id = book.Id;

        await _booksService.UpdateAsync(id, updatedBook);

        return NoContent();
    }

    [HttpDelete("{id:length(24)}")]
    public async Task<IActionResult> Delete(string id)
    {
        var book = await _booksService.GetAsync(id);

        if (book is null)
        {
            return NotFound();
        }

        await _booksService.RemoveAsync(id);

        return NoContent();
    }
}

El controlador de API web anterior:

  • Usa la clase BooksService para ejecutar operaciones CRUD.
  • Contiene métodos de acción para admitir las solicitudes GET, POST, PUT y DELETE de HTTP.
  • Llama a CreatedAtAction en el método de acción Create para devolver una respuesta CreatedAtAction. El código de estado 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor. CreatedAtAction también agrega un encabezado Location a la respuesta. El encabezado Location especifica el identificador URI del libro recién creado.

Prueba de la API web

  1. Compile y ejecute la aplicación.

  2. Vaya a https://localhost:<port>/api/books, donde <port> es el número de puerto asignado automáticamente para la aplicación, para probar el método de acción Get sin parámetros del controlador. Se muestra una respuesta JSON similar a la siguiente:

    [
      {
        "id": "61a6058e6c43f32854e51f51",
        "bookName": "Design Patterns",
        "price": 54.93,
        "category": "Computers",
        "author": "Ralph Johnson"
      },
      {
        "id": "61a6058e6c43f32854e51f52",
        "bookName": "Clean Code",
        "price": 43.15,
        "category": "Computers",
        "author": "Robert C. Martin"
      }
    ]
    
  3. Vaya a https://localhost:<port>/api/books/{id here} para probar el método de acción Get sobrecargado del controlador. Se muestra una respuesta JSON similar a la siguiente:

    {
      "id": "61a6058e6c43f32854e51f52",
      "bookName": "Clean Code",
      "price": 43.15,
      "category": "Computers",
      "author": "Robert C. Martin"
    }
    

Configuración de las opciones de serialización de JSON

Hay dos detalles que cambiar sobre las respuestas JSON devueltas en la sección Prueba de la API web:

  • Las mayúsculas y minúsculas Camel predeterminadas de los nombres de propiedad se deben cambiar para que coincidan con el uso de mayúsculas y minúsculas de Pascal de los nombres de propiedad del objeto CLR.
  • La propiedad bookName se debe devolver como Name.

Para satisfacer los requisitos anteriores, realice los cambios siguientes:

  1. En Program.cs, cambie el código resaltado siguiente en la llamada al método AddControllers:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<BookStoreDatabaseSettings>(
        builder.Configuration.GetSection("BookStoreDatabase"));
    
    builder.Services.AddSingleton<BooksService>();
    
    builder.Services.AddControllers()
        .AddJsonOptions(
            options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
    

    Con el cambio anterior, los nombres de propiedad de la respuesta JSON serializada de la API web coinciden con sus nombres de propiedad correspondientes en el tipo de objeto CLR. Por ejemplo, la propiedad Author de la clase Book se serializa como Author, en lugar de author.

  2. En Models/Book.cs, anote la propiedad BookName con el atributo [JsonPropertyName]:

    [BsonElement("Name")]
    [JsonPropertyName("Name")]
    public string BookName { get; set; } = null!;
    

    El valor Name del atributo [JsonPropertyName] representa el nombre de propiedad en la respuesta JSON serializada de la API web.

  3. Agregue el código siguiente en la parte superior de Models/Book.cs para resolver la referencia al atributo [JsonProperty]:

    using System.Text.Json.Serialization;
    
  4. Repita los pasos definidos en la sección Prueba de la API web. Observe la diferencia en los nombres de propiedad JSON.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Recursos adicionales

En este tutorial se crea una API web que ejecuta operaciones de creación, lectura, actualización y eliminación (CRUD) en una base de datos NoSQL de MongoDB.

En este tutorial aprenderá a:

  • Configurar MongoDB
  • Crear una base de datos de MongoDB
  • Definir un esquema y una colección de MongoDB
  • Realizar operaciones de CRUD de MongoDB desde una API web
  • Personalizar la serialización de JSON

Vea o descargue el código de ejemplo (cómo descargarlo)

Requisitos previos

Configurar MongoDB

Si usa Windows, MongoDB está instalado en C:\Archivos de programa\MongoDB de forma predeterminada. Agregue C:\Archivos de programa\MongoDB\Server<número_versión>\bin a la variable de entorno Path. Este cambio permite el acceso a MongoDB desde cualquier lugar en el equipo de desarrollo.

Use el Shell de mongo en los pasos siguientes para crear una base de datos, hacer colecciones y almacenar documentos. Para obtener más información sobre los comandos de Shell de mongo, consulte Working with the mongo Shell (Trabajo con el shell de Mongo).

  1. Elija un directorio en el equipo de desarrollo para almacenar los datos. Por ejemplo, C:\BooksData en Windows. Si no existe el directorio, créelo. El shell de mongo no crea nuevos directorios.

  2. Abra un shell de comandos. Ejecute el comando siguiente para conectarse a MongoDB en el puerto predeterminado 27017. No olvide reemplazar <data_directory_path> por el directorio que eligió en el paso anterior.

    mongod --dbpath <data_directory_path>
    
  3. Abra otra instancia del shell de comandos. Conéctese a la base de datos de prueba de forma predeterminada ejecutando el comando siguiente:

    mongo
    
  4. Ejecute el comando siguiente en un shell de comandos:

    use BookstoreDb
    

    Se crea una base de datos denominada BookstoreDb si aún no existe. Si la base de datos existe, su conexión se abre para las transacciones.

  5. Cree una colección Books con el comando siguiente:

    db.createCollection('Books')
    

    Se muestra el siguiente resultado:

    { "ok" : 1 }
    
  6. Defina un esquema para la colección Books e inserte dos documentos con el comando siguiente:

    db.Books.insertMany([{'Name':'Design Patterns','Price':54.93,'Category':'Computers','Author':'Ralph Johnson'}, {'Name':'Clean Code','Price':43.15,'Category':'Computers','Author':'Robert C. Martin'}])
    

    Se muestra el siguiente resultado:

    {
      "acknowledged" : true,
      "insertedIds" : [
        ObjectId("5bfd996f7b8e48dc15ff215d"),
        ObjectId("5bfd996f7b8e48dc15ff215e")
      ]
    }
    

    Nota:

    Los identificadores que se muestran en este artículo no coinciden con los que se mostrarán cuando ejecute este ejemplo.

  7. Vea los documentos en la base de datos mediante el comando siguiente:

    db.Books.find({}).pretty()
    

    Se muestra el siguiente resultado:

    {
      "_id" : ObjectId("5bfd996f7b8e48dc15ff215d"),
      "Name" : "Design Patterns",
      "Price" : 54.93,
      "Category" : "Computers",
      "Author" : "Ralph Johnson"
    }
    {
      "_id" : ObjectId("5bfd996f7b8e48dc15ff215e"),
      "Name" : "Clean Code",
      "Price" : 43.15,
      "Category" : "Computers",
      "Author" : "Robert C. Martin"
    }
    

    El esquema agrega una propiedad _id generada automáticamente del tipo ObjectId para cada documento.

La base de datos está lista. Puede empezar a crear la API web de ASP.NET Core.

Creación de un proyecto de API web de ASP.NET Core

  1. Vaya a Archivo>Nuevo>Proyecto.

  2. Seleccione el tipo de proyecto Aplicación web de ASP.NET Core y, luego, Siguiente.

  3. Denomine el proyecto BooksApi y seleccione Crear.

  4. Seleccione .NET Core como plataforma de destino y ASP.NET Core 3.0. Seleccione la plantilla de proyecto API y, luego, Crear.

  5. Visite la galería de NuGet: MongoDB.Driver para determinar la última versión estable del controlador .NET para MongoDB. En la ventana Consola del Administrador de paquetes, desplácese hasta la raíz del proyecto. Ejecute el siguiente comando para instalar el controlador .NET para MongoDB:

    Install-Package MongoDB.Driver -Version {VERSION}
    

Adición de un modelo de entidad

  1. Agregue un directorio Modelos a la raíz del proyecto.

  2. Agregue una clase Book al directorio Book con el código siguiente:

    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace BooksApi.Models
    {
        public class Book
        {
            [BsonId]
            [BsonRepresentation(BsonType.ObjectId)]
            public string Id { get; set; }
    
            [BsonElement("Name")]
            public string BookName { get; set; }
    
            public decimal Price { get; set; }
    
            public string Category { get; set; }
    
            public string Author { get; set; }
        }
    }
    

    En la clase anterior, se requiere la propiedad Id:

    La propiedad BookName se anota con el atributo [BsonElement]. El valor Name del atributo representa el nombre de propiedad en la colección de MongoDB.

Adición de un modelo configuración

  1. Agregue los siguientes valores de configuración de base de datos a appsettings.json :

    {
      "BookstoreDatabaseSettings": {
        "BooksCollectionName": "Books",
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookstoreDb"
      },
      "Logging": {
        "IncludeScopes": false,
        "Debug": {
          "LogLevel": {
            "Default": "Warning"
          }
        },
        "Console": {
          "LogLevel": {
            "Default": "Warning"
          }
        }
      }
    }
    
  2. Agregue un archivo BookstoreDatabaseSettings.cs al directorio Models con el código siguiente:

    namespace BooksApi.Models
    {
        public class BookstoreDatabaseSettings : IBookstoreDatabaseSettings
        {
            public string BooksCollectionName { get; set; }
            public string ConnectionString { get; set; }
            public string DatabaseName { get; set; }
        }
    
        public interface IBookstoreDatabaseSettings
        {
            string BooksCollectionName { get; set; }
            string ConnectionString { get; set; }
            string DatabaseName { get; set; }
        }
    }
    

    La clase anterior BookstoreDatabaseSettings se utiliza para almacenar los valores de propiedad BookstoreDatabaseSettings del archivo appsettings.json . Los nombres de las propiedades de JSON y C# son iguales para facilitar el proceso de asignación.

  3. Agregue el código resaltado siguiente a Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        // requires using Microsoft.Extensions.Options
        services.Configure<BookstoreDatabaseSettings>(
            Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
    
        services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
            sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);
    
        services.AddControllers();
    }
    

    En el código anterior:

    • La instancia de configuración a la que la sección BookstoreDatabaseSettings del archivo appsettings.json enlaza está registrada en el contenedor de inserción de dependencias (DI). Por ejemplo, una propiedad ConnectionString del objeto BookstoreDatabaseSettings se rellena con la propiedad BookstoreDatabaseSettings:ConnectionString en appsettings.json .
    • La interfaz IBookstoreDatabaseSettings se registra en la inserción de dependencias con una IBookstoreDatabaseSettings de tipo singleton. Cuando se inserta, la instancia de la interfaz se resuelve en un objeto BookstoreDatabaseSettings.
  4. Agregue el código siguiente en la parte superior del archivo Startup.cs para resolver las referencia a BookstoreDatabaseSettings y IBookstoreDatabaseSettings:

    using BooksApi.Models;
    

Adición de un servicio de operaciones CRUD

  1. Agregue un directorio Servicios a la raíz del proyecto.

  2. Agregue una clase BookService al directorio BookService con el código siguiente:

    using BooksApi.Models;
    using MongoDB.Driver;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace BooksApi.Services
    {
        public class BookService
        {
            private readonly IMongoCollection<Book> _books;
    
            public BookService(IBookstoreDatabaseSettings settings)
            {
                var client = new MongoClient(settings.ConnectionString);
                var database = client.GetDatabase(settings.DatabaseName);
    
                _books = database.GetCollection<Book>(settings.BooksCollectionName);
            }
    
            public List<Book> Get() =>
                _books.Find(book => true).ToList();
    
            public Book Get(string id) =>
                _books.Find<Book>(book => book.Id == id).FirstOrDefault();
    
            public Book Create(Book book)
            {
                _books.InsertOne(book);
                return book;
            }
    
            public void Update(string id, Book bookIn) =>
                _books.ReplaceOne(book => book.Id == id, bookIn);
    
            public void Remove(Book bookIn) =>
                _books.DeleteOne(book => book.Id == bookIn.Id);
    
            public void Remove(string id) => 
                _books.DeleteOne(book => book.Id == id);
        }
    }
    

    En el código anterior, se recuperó una instancia de IBookstoreDatabaseSettings de la inserción de dependencias mediante la inserción de un constructor. Esta técnica proporciona acceso a los valores de configuración de appsettings.json que se agregaron en la sección appsettings.json.

  3. Agregue el código resaltado siguiente a Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<BookstoreDatabaseSettings>(
            Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
    
        services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
            sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);
    
        services.AddSingleton<BookService>();
    
        services.AddControllers();
    }
    

    En el código anterior, la clase BookService se registra con inserción de dependencias para admitir la inserción del constructor en las clases de consumo. La duración de servicio de tipo singleton es la más adecuada porque BookService toma una dependencia directa sobre MongoClient. Según las instrucciones oficiales de reutilización de cliente Mongo, MongoClient debe registrarse en la inserción de dependencias con una duración de servicio de tipo singleton.

  4. Agregue el código siguiente en la parte superior del archivo Startup.cs para resolver la referencia a BookService:

    using BooksApi.Services;
    

La clase BookService usa los miembros MongoDB.Driver siguientes para ejecutar operaciones CRUD en la base de datos:

  • MongoClient: lee la instancia del servidor para ejecutar operaciones de base de datos. Se proporciona la cadena de conexión de MongoDB al constructor de esta clase:

    public BookService(IBookstoreDatabaseSettings settings)
    {
        var client = new MongoClient(settings.ConnectionString);
        var database = client.GetDatabase(settings.DatabaseName);
    
        _books = database.GetCollection<Book>(settings.BooksCollectionName);
    }
    
  • IMongoDatabase: representa la base de datos de Mongo para ejecutar operaciones. Este tutorial usa el método genérico GetCollection<TDocument>(collection) en la interfaz para tener acceso a los datos de una colección específica. Ejecute las operaciones CRUD en la colección después de llamar a este método. En la llamada al método GetCollection<TDocument>(collection):

    • collection representa el nombre de la colección.
    • TDocument representa el tipo de objeto CLR almacenado en la colección.

GetCollection<TDocument>(collection) devuelve un objeto GetCollection<TDocument>(collection) que representa la colección. En este tutorial, se invocan los métodos siguientes en la colección:

  • DeleteOne: elimina un único documento que cumple los criterios de búsqueda proporcionados.
  • Find<TDocument>: devuelve todos los documentos de la colección que cumplen los criterios de búsqueda indicados.
  • InsertOne: inserta el objeto proporcionado como un nuevo documento en la colección.
  • ReplaceOne: reemplaza un único documento que cumple los criterios de búsqueda indicados por el objeto proporcionado.

Incorporación de un controlador

Agregue una clase BooksController al directorio BooksController con el código siguiente:

using BooksApi.Models;
using BooksApi.Services;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace BooksApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BooksController : ControllerBase
    {
        private readonly BookService _bookService;

        public BooksController(BookService bookService)
        {
            _bookService = bookService;
        }

        [HttpGet]
        public ActionResult<List<Book>> Get() =>
            _bookService.Get();

        [HttpGet("{id:length(24)}", Name = "GetBook")]
        public ActionResult<Book> Get(string id)
        {
            var book = _bookService.Get(id);

            if (book == null)
            {
                return NotFound();
            }

            return book;
        }

        [HttpPost]
        public ActionResult<Book> Create(Book book)
        {
            _bookService.Create(book);

            return CreatedAtRoute("GetBook", new { id = book.Id.ToString() }, book);
        }

        [HttpPut("{id:length(24)}")]
        public IActionResult Update(string id, Book bookIn)
        {
            var book = _bookService.Get(id);

            if (book == null)
            {
                return NotFound();
            }

            _bookService.Update(id, bookIn);

            return NoContent();
        }

        [HttpDelete("{id:length(24)}")]
        public IActionResult Delete(string id)
        {
            var book = _bookService.Get(id);

            if (book == null)
            {
                return NotFound();
            }

            _bookService.Remove(id);

            return NoContent();
        }
    }
}

El controlador de API web anterior:

  • Usa la clase BookService para ejecutar operaciones CRUD.
  • Contiene métodos de acción para admitir las solicitudes GET, POST, PUT y DELETE de HTTP.
  • Llama a CreatedAtRoute en el método de acción Create para devolver una respuesta CreatedAtRoute. El código de estado 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor. CreatedAtRoute también agrega un encabezado Location a la respuesta. El encabezado Location especifica el identificador URI del libro recién creado.

Prueba de la API web

  1. Compile y ejecute la aplicación.

  2. Vaya a https://localhost:<port>/api/books para probar el método de acción Get sin parámetros del controlador. Se muestra la siguiente respuesta JSON:

    [
      {
        "id":"5bfd996f7b8e48dc15ff215d",
        "bookName":"Design Patterns",
        "price":54.93,
        "category":"Computers",
        "author":"Ralph Johnson"
      },
      {
        "id":"5bfd996f7b8e48dc15ff215e",
        "bookName":"Clean Code",
        "price":43.15,
        "category":"Computers",
        "author":"Robert C. Martin"
      }
    ]
    
  3. Vaya a https://localhost:<port>/api/books/{id here} para probar el método de acción Get sobrecargado del controlador. Se muestra la siguiente respuesta JSON:

    {
      "id":"{ID}",
      "bookName":"Clean Code",
      "price":43.15,
      "category":"Computers",
      "author":"Robert C. Martin"
    }
    

Configuración de las opciones de serialización de JSON

Hay dos detalles que cambiar sobre las respuestas JSON devueltas en la sección Prueba de la API web:

  • Las mayúsculas y minúsculas Camel predeterminadas de los nombres de propiedad se deben cambiar para que coincidan con el uso de mayúsculas y minúsculas de Pascal de los nombres de propiedad del objeto CLR.
  • La propiedad bookName se debe devolver como Name.

Para satisfacer los requisitos anteriores, realice los cambios siguientes:

  1. JSON.NET se ha quitado de la plataforma compartida de ASP.NET. Agregue una referencia de paquete a Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  2. En Startup.ConfigureServices, cambie el código resaltado siguiente en la llamada al método AddControllers:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<BookstoreDatabaseSettings>(
            Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
    
        services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
            sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);
    
        services.AddSingleton<BookService>();
    
        services.AddControllers()
            .AddNewtonsoftJson(options => options.UseMemberCasing());
    }
    

    Con el cambio anterior, los nombres de propiedad de la respuesta JSON serializada de la API web coinciden con sus nombres de propiedad correspondientes en el tipo de objeto CLR. Por ejemplo, la propiedad Author de la clase Book se serializa como Author.

  3. En Models/Book.csModels/Book.cs, anote la propiedad Models/Book.cs con el atributo [JsonProperty] siguiente:

    [BsonElement("Name")]
    [JsonProperty("Name")]
    public string BookName { get; set; }
    

    El valor Name del atributo [JsonProperty] representa el nombre de propiedad en la respuesta JSON serializada de la API web.

  4. Agregue el código siguiente en la parte superior de Models/Book.cs para resolver la referencia al atributo [JsonProperty]:

    using Newtonsoft.Json;
    
  5. Repita los pasos definidos en la sección Prueba de la API web. Observe la diferencia en los nombres de propiedad JSON.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende IdentityServer es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende IdentityServer permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Para más información, consulte Información general de Duende IdentityServer.

Para más información sobre otros proveedores de autenticación, consulte Opciones de autenticación de OSS de la comunidad para ASP.NET Core.

Pasos siguientes

Para obtener más información sobre la creación de las API web de ASP.NET Core, consulte los siguientes recursos: