Creeu una API web amb ASP.NET Core i MongoDB

Note

This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.

Per Pratik Khandelwal i Scott Addie

Aquest tutorial crea una API web que executa operacions de creació, lectura, actualització i supressió (CRUD) en una base de dades NoSQL de MongoDB .

En aquest tutorial, aprendràs a:

  • Configura MongoDB
  • Creeu una base de dades MongoDB
  • Definiu una col·lecció i un esquema MongoDB
  • Realitzeu operacions MongoDB CRUD des d'una API web
  • Personalitza la serialització JSON

Requisits previs

Configura MongoDB

Activeu l'accés a MongoDB i Mongo DB Shell des de qualsevol lloc de la màquina de desenvolupament:

  1. A Windows, MongoDB s'instal·la per defecte a C:\Program Files\MongoDB . Afegiu C:\Program Files\MongoDB\Server\<versió_número>\bin a la PATHvariable d'entorn.

  2. Baixeu el MongoDB Shell i trieu un directori per extreure-lo. Afegiu el camí resultant per mongosh.exea la PATHvariable d'entorn.

  3. Trieu un directori a la màquina de desenvolupament per emmagatzemar les dades. Per exemple, C:\BooksData a Windows. Creeu el directori si no existeix. El mongo Shell no crea directoris nous.

  4. A l'intèrpret d'ordres del sistema operatiu (no el MongoDB Shell), utilitzeu l'ordre següent per connectar-vos a MongoDB al port predeterminat 27017. Substituïu- <data_directory_path>lo pel directori escollit al pas anterior.

    Consola
    mongod --dbpath <data_directory_path>
    

Utilitzeu el MongoDB Shell instal·lat anteriorment en els passos següents per crear una base de dades, fer col·leccions i emmagatzemar documents. Per obtenir més informació sobre les ordres de MongoDB Shell, vegeu mongosh.

  1. Obriu una instància de l'intèrpret d'ordres de MongoDB llançant mongosh.exe.

  2. A l'intèrpret d'ordres, connecteu-vos a la base de dades de prova predeterminada executant l'ordre següent:

    Consola
    mongosh
    
  3. Executeu l'ordre següent a l'intèrpret d'ordres:

    Consola
    use BookStore
    

    Es crea una base de dades anomenada BookStore si encara no existeix. Si la base de dades existeix, la seva connexió s'obre per a transaccions.

  4. Creeu una Bookscol·lecció mitjançant l'ordre següent:

    Consola
    db.createCollection('Books')
    

    Es mostra el resultat següent:

    Consola
    { "ok" : 1 }
    
  5. Definiu un esquema per a la Bookscol·lecció i inseriu dos documents amb l'ordre següent:

    Consola
    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" }])
    

    Es mostra un resultat similar al següent:

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

    Nota

    Les ObjectIds que es mostren al resultat anterior no coincidiran amb les que es mostren a l'intèrpret d'ordres.

  6. Visualitzeu els documents a la base de dades mitjançant l'ordre següent:

    Consola
    db.Books.find().pretty()
    

    Es mostra un resultat similar al següent:

    Consola
    {
         "_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"
     }
    

    L'esquema afegeix una _idpropietat de tipus generada automàticament ObjectIdper a cada document.

Creeu el projecte de l'API web ASP.NET Core

  1. Aneu a Fitxer > Nou > Projecte .

  2. Seleccioneu el tipus de projecte de l'API web ASP.NET Core i seleccioneu Següent .

  3. Anomeneu el projecte BookStoreApi i seleccioneu Següent .

  4. Seleccioneu el marc .NET 8.0 (suport a llarg termini) i seleccioneu Crea .

  5. A la finestra de la consola del gestor de paquets , aneu a l'arrel del projecte. Executeu l'ordre següent per instal·lar el controlador .NET per a MongoDB:

    PowerShell
    Install-Package MongoDB.Driver
    

Afegeix un model d'entitat

  1. Afegiu un directori Models a l'arrel del projecte.

  2. Afegiu una Bookclasse al directori Models amb el codi següent:

    C#
    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!;
    }
    

    A la classe anterior, la Idpropietat és:

    • Necessari per assignar l'objecte Common Language Runtime (CLR) a la col·lecció MongoDB.
    • Anotat amb [BsonId]per fer d'aquesta propietat la clau primària del document.
    • Anotat amb [BsonRepresentation(BsonType.ObjectId)]per permetre passar el paràmetre com a tipus stringen lloc d'una estructura ObjectId . Mongo gestiona la conversió de stringa ObjectId.

    La BookNamepropietat s'anota amb l' [BsonElement]atribut. El valor de l'atribut de Namerepresenta el nom de la propietat a la col·lecció MongoDB.

Afegiu un model de configuració

  1. Afegiu els valors de configuració de la base de dades següents a appsettings.json:

    JSON
    {
        "BookStoreDatabase": {
            "ConnectionString": "mongodb://localhost:27017",
            "DatabaseName": "BookStore",
            "BooksCollectionName": "Books"
        },
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.AspNetCore": "Warning"
            }
        },
        "AllowedHosts": "*"
    }
    
  2. Afegiu una BookStoreDatabaseSettingsclasse al directori Models amb el codi següent:

    C#
    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 BookStoreDatabaseSettingsclasse anterior s'utilitza per emmagatzemar els valors de propietat appsettings.jsondel fitxer BookStoreDatabase. Els noms de propietat JSON i C# s'anomenen de manera idèntica per facilitar el procés de mapatge.

  3. Afegiu el següent codi destacat a Program.cs:

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

    Al codi anterior, la instància de configuració a la qual s'uneix la secció appsettings.jsondel fitxer BookStoreDatabaseestà registrada al contenidor d'injecció de dependències (DI). Per exemple, la propietat BookStoreDatabaseSettingsde l'objecte ConnectionStrings'emplena amb la BookStoreDatabase:ConnectionStringpropietat a appsettings.json.

  4. Afegiu el codi següent a la part superior de Program.csper resoldre la BookStoreDatabaseSettingsreferència:

    C#
    using BookStoreApi.Models;
    

Afegiu un servei d'operacions CRUD

  1. Afegiu un directori de serveis a l'arrel del projecte.

  2. Afegiu una BooksServiceclasse al directori de serveis amb el codi següent:

    C#
    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);
    }
    

    Al codi anterior, BookStoreDatabaseSettingses recupera una instància de DI mitjançant la injecció de constructor. Aquesta tècnica proporciona accés als appsettings.jsonvalors de configuració que s'han afegit a la secció Afegeix un model de configuració .

  3. Afegiu el següent codi destacat a Program.cs:

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

    Al codi anterior, la BooksServiceclasse està registrada amb DI per donar suport a la injecció de constructor a les classes consumidores. La vida útil del servei singleton és la més adequada perquè BooksServicedepèn directament de MongoClient. D'acord amb les directrius oficials de reutilització del client Mongo , MongoClients'ha de registrar a DI amb una vida útil de servei singleton.

  4. Afegiu el codi següent a la part superior de Program.csper resoldre la BooksServicereferència:

    C#
    using BookStoreApi.Services;
    

La BooksServiceclasse utilitza els MongoDB.Drivermembres següents per executar operacions CRUD amb la base de dades:

  • MongoClient : llegeix la instància del servidor per executar operacions de base de dades. Al constructor d'aquesta classe se li proporciona la cadena de connexió MongoDB:

    C#
    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 dades Mongo per executar operacions. Aquest tutorial utilitza el mètode genèric GetCollection<TDocument>(col·lecció) a la interfície per accedir a les dades d'una col·lecció específica. Executeu operacions CRUD contra la col·lecció després de cridar aquest mètode. A la GetCollection<TDocument>(collection)trucada del mètode:

    • collectionrepresenta el nom de la col·lecció.
    • TDocumentrepresenta el tipus d'objecte CLR emmagatzemat a la col·lecció.

GetCollection<TDocument>(collection)retorna un objecte MongoCollection que representa la col·lecció. En aquest tutorial, s'invoquen els mètodes següents a la col·lecció:

  • DeleteOneAsync : elimina un sol document que coincideix amb els criteris de cerca proporcionats.
  • Find<TDocument> : retorna tots els documents de la col·lecció que coincideixen amb els criteris de cerca proporcionats.
  • InsertOneAsync : Insereix l'objecte proporcionat com a document nou a la col·lecció.
  • ReplaceOneAsync : Substitueix el document únic que coincideix amb els criteris de cerca proporcionats amb l'objecte proporcionat.

Afegiu un controlador

Afegiu una BooksControllerclasse al directori Controllers amb el codi següent:

C#
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 l'API web anterior:

  • Utilitza la BooksServiceclasse per executar operacions CRUD.
  • Conté mètodes d'acció per admetre sol·licituds HTTP GET, POST, PUT i DELETE.
  • Crida a CreatedAtAction al Createmètode d'acció per retornar una resposta HTTP 201 . El codi d'estat 201 és la resposta estàndard per a un mètode HTTP POST que crea un recurs nou al servidor. CreatedAtActiontambé afegeix una Locationcapçalera a la resposta. La Locationcapçalera especifica l'URI del llibre acabat de crear.

Proveu l'API web

  1. Creeu i executeu l'aplicació.

  2. Navegueu a https://localhost:<port>/api/books, on <port>és el número de port assignat automàticament per a l'aplicació, per provar el Getmètode d'acció sense paràmetres del controlador. Es mostra una resposta JSON semblant a la següent:

    JSON
    [
      {
        "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. Navegueu a https://localhost:<port>/api/books/{id here}per provar el Getmètode d'acció sobrecarregat del controlador. Es mostra una resposta JSON semblant a la següent:

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

Configura les opcions de serialització JSON

Hi ha dos detalls per canviar sobre les respostes JSON retornades a la secció Prova l'API web :

  • La minúscula per defecte dels noms de propietat s'ha de canviar perquè coincideixi amb la minúscula Pascal dels noms de propietat de l'objecte CLR.
  • La bookNamepropietat s'ha de retornar com a Name.

Per complir els requisits anteriors, feu els canvis següents:

  1. A Program.cs, encadeneu el codi ressaltat següent a la AddControllerstrucada al mètode:

    C#
    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);
    

    Amb el canvi anterior, els noms de propietat de la resposta JSON serialitzada de l'API web coincideixen amb els noms de propietat corresponents al tipus d'objecte CLR. Per exemple, la propietat Bookde la classe es serialitza com en lloc de .AuthorAuthorauthor

  2. A Models/Book.cs, anoteu la BookNamepropietat amb l' [JsonPropertyName]atribut:

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

    El [JsonPropertyName]valor de l'atribut de Namerepresenta el nom de la propietat a la resposta JSON serialitzada de l'API web.

  3. Afegiu el codi següent a la part superior de Models/Book.csper resoldre la [JsonProperty]referència de l'atribut:

    C#
    using System.Text.Json.Serialization;
    
  4. Repetiu els passos definits a la secció Prova l'API web . Observeu la diferència en els noms de propietat JSON.

Afegiu suport d'autenticació a una API web

ASP.NET Core Identity afegeix la funcionalitat d'inici de sessió de la interfície d'usuari (UI) a les aplicacions web ASP.NET Core. Per protegir les API web i els SPA, utilitzeu una de les opcions següents:

Duende Identity Server és un marc OpenID Connect i OAuth 2.0 per a ASP.NET Core. Duende Identity Server habilita les funcions de seguretat següents:

  • Autenticació com a servei (AaaS)
  • Inici/desactivació únic (SSO) en diversos tipus d'aplicacions
  • Control d'accés a les API
  • Portal de la Federació

Important

Duende Software pot requerir que pagueu una tarifa de llicència per a l'ús de producció del Duende Identity Server. Per obtenir més informació, vegeu Migrar d'ASP.NET Core 5.0 a 6.0 .

Per obtenir més informació, consulteu la documentació del Duende Identity Server (lloc web de Duende Software) .

Recursos addicionals

This tutorial creates a web API that runs Create, Read, Update, and Delete (CRUD) operations on a MongoDB NoSQL database.

In this tutorial, you learn how to:

  • Configure MongoDB
  • Create a MongoDB database
  • Define a MongoDB collection and schema
  • Perform MongoDB CRUD operations from a web API
  • Customize JSON serialization

Prerequisites

Configure MongoDB

Enable MongoDB and Mongo DB Shell access from anywhere on the development machine:

  1. On Windows, MongoDB is installed at C:\Program Files\MongoDB by default. Add C:\Program Files\MongoDB\Server\<version_number>\bin to the PATH environment variable.

  2. Download the MongoDB Shell and choose a directory to extract it to. Add the resulting path for mongosh.exe to the PATH environment variable.

  3. Choose a directory on the development machine for storing the data. For example, C:\BooksData on Windows. Create the directory if it doesn't exist. The mongo Shell doesn't create new directories.

  4. In the OS command shell (not the MongoDB Shell), use the following command to connect to MongoDB on default port 27017. Replace <data_directory_path> with the directory chosen in the previous step.

    Console
    mongod --dbpath <data_directory_path>
    

Use the previously installed MongoDB Shell in the following steps to create a database, make collections, and store documents. For more information on MongoDB Shell commands, see mongosh.

  1. Open a MongoDB command shell instance by launching mongosh.exe.

  2. In the command shell connect to the default test database by running the following command:

    Console
    mongosh
    
  3. Run the following command in the command shell:

    Console
    use BookStore
    

    A database named BookStore is created if it doesn't already exist. If the database does exist, its connection is opened for transactions.

  4. Create a Books collection using following command:

    Console
    db.createCollection('Books')
    

    The following result is displayed:

    Console
    { "ok" : 1 }
    
  5. Define a schema for the Books collection and insert two documents using the following command:

    Console
    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" }])
    

    A result similar to the following is displayed:

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

    Note

    The ObjectIds shown in the preceding result won't match those shown in the command shell.

  6. View the documents in the database using the following command:

    Console
    db.Books.find().pretty()
    

    A result similar to the following is displayed:

    Console
    {
         "_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"
     }
    

    The schema adds an autogenerated _id property of type ObjectId for each document.

Create the ASP.NET Core web API project

  1. Go to File > New > Project.

  2. Select the ASP.NET Core Web API project type, and select Next.

  3. Name the project BookStoreApi, and select Next.

  4. Select the .NET 7.0 (Standard Term Support) framework and select Create.

  5. From the Tools menu, select NuGet Package Manager > Package Manager Console.

  6. In the Package Manager Console window, navigate to the project root. Run the following command to install the .NET driver for MongoDB:

    PowerShell
    Install-Package MongoDB.Driver
    

Add an entity model

  1. Add a Models directory to the project root.

  2. Add a Book class to the Models directory with the following code:

    C#
    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!;
    }
    

    In the preceding class, the Id property is:

    • Required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
    • Annotated with [BsonId] to make this property the document's primary key.
    • Annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.

    The BookName property is annotated with the [BsonElement] attribute. The attribute's value of Name represents the property name in the MongoDB collection.

Add a configuration model

  1. Add the following database configuration values to appsettings.json:

    JSON
    {
      "BookStoreDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookStore",
        "BooksCollectionName": "Books"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    
  2. Add a BookStoreDatabaseSettings class to the Models directory with the following code:

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

    The preceding BookStoreDatabaseSettings class is used to store the appsettings.json file's BookStoreDatabase property values. The JSON and C# property names are named identically to ease the mapping process.

  3. Add the following highlighted code to Program.cs:

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

    In the preceding code, the configuration instance to which the appsettings.json file's BookStoreDatabase section binds is registered in the Dependency Injection (DI) container. For example, the BookStoreDatabaseSettings object's ConnectionString property is populated with the BookStoreDatabase:ConnectionString property in appsettings.json.

  4. Add the following code to the top of Program.cs to resolve the BookStoreDatabaseSettings reference:

    C#
    using BookStoreApi.Models;
    

Add a CRUD operations service

  1. Add a Services directory to the project root.

  2. Add a BooksService class to the Services directory with the following code:

    C#
    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);
    }
    

    In the preceding code, a BookStoreDatabaseSettings instance is retrieved from DI via constructor injection. This technique provides access to the appsettings.json configuration values that were added in the Add a configuration model section.

  3. Add the following highlighted code to Program.cs:

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

    In the preceding code, the BooksService class is registered with DI to support constructor injection in consuming classes. The singleton service lifetime is most appropriate because BooksService takes a direct dependency on MongoClient. Per the official Mongo Client reuse guidelines, MongoClient should be registered in DI with a singleton service lifetime.

  4. Add the following code to the top of Program.cs to resolve the BooksService reference:

    C#
    using BookStoreApi.Services;
    

The BooksService class uses the following MongoDB.Driver members to run CRUD operations against the database:

  • MongoClient: Reads the server instance for running database operations. The constructor of this class is provided the MongoDB connection string:

    C#
    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: Represents the Mongo database for running operations. This tutorial uses the generic GetCollection<TDocument>(collection) method on the interface to gain access to data in a specific collection. Run CRUD operations against the collection after this method is called. In the GetCollection<TDocument>(collection) method call:

    • collection represents the collection name.
    • TDocument represents the CLR object type stored in the collection.

GetCollection<TDocument>(collection) returns a MongoCollection object representing the collection. In this tutorial, the following methods are invoked on the collection:

  • DeleteOneAsync: Deletes a single document matching the provided search criteria.
  • Find<TDocument>: Returns all documents in the collection matching the provided search criteria.
  • InsertOneAsync: Inserts the provided object as a new document in the collection.
  • ReplaceOneAsync: Replaces the single document matching the provided search criteria with the provided object.

Add a controller

Add a BooksController class to the Controllers directory with the following code:

C#
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();
    }
}

The preceding web API controller:

  • Uses the BooksService class to run CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.
  • Calls CreatedAtAction in the Create action method to return an HTTP 201 response. Status code 201 is the standard response for an HTTP POST method that creates a new resource on the server. CreatedAtAction also adds a Location header to the response. The Location header specifies the URI of the newly created book.

Test the web API

  1. Build and run the app.

  2. Navigate to https://localhost:<port>/api/books, where <port> is the automatically assigned port number for the app, to test the controller's parameterless Get action method. A JSON response similar to the following is displayed:

    JSON
    [
      {
        "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. Navigate to https://localhost:<port>/api/books/{id here} to test the controller's overloaded Get action method. A JSON response similar to the following is displayed:

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

Configure JSON serialization options

There are two details to change about the JSON responses returned in the Test the web API section:

  • The property names' default camel casing should be changed to match the Pascal casing of the CLR object's property names.
  • The bookName property should be returned as Name.

To satisfy the preceding requirements, make the following changes:

  1. In Program.cs, chain the following highlighted code on to the AddControllers method call:

    C#
    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);
    

    With the preceding change, property names in the web API's serialized JSON response match their corresponding property names in the CLR object type. For example, the Book class's Author property serializes as Author instead of author.

  2. In Models/Book.cs, annotate the BookName property with the [JsonPropertyName] attribute:

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

    The [JsonPropertyName] attribute's value of Name represents the property name in the web API's serialized JSON response.

  3. Add the following code to the top of Models/Book.cs to resolve the [JsonProperty] attribute reference:

    C#
    using System.Text.Json.Serialization;
    
  4. Repeat the steps defined in the Test the web API section. Notice the difference in JSON property names.

Add authentication support to a web API

ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following:

Duende Identity Server is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Duende Identity Server enables the following security features:

  • Authentication as a Service (AaaS)
  • Single sign-on/off (SSO) over multiple application types
  • Access control for APIs
  • Federation Gateway

Important

Duende Software might require you to pay a license fee for production use of Duende Identity Server. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.

For more information, see the Duende Identity Server documentation (Duende Software website).

Additional resources

This tutorial creates a web API that runs Create, Read, Update, and Delete (CRUD) operations on a MongoDB NoSQL database.

In this tutorial, you learn how to:

  • Configure MongoDB
  • Create a MongoDB database
  • Define a MongoDB collection and schema
  • Perform MongoDB CRUD operations from a web API
  • Customize JSON serialization

Prerequisites

Configure MongoDB

Enable MongoDB and Mongo DB Shell access from anywhere on the development machine:

  1. On Windows, MongoDB is installed at C:\Program Files\MongoDB by default. Add C:\Program Files\MongoDB\Server\<version_number>\bin to the PATH environment variable.

  2. Download the MongoDB Shell and choose a directory to extract it to. Add the resulting path for mongosh.exe to the PATH environment variable.

  3. Choose a directory on the development machine for storing the data. For example, C:\BooksData on Windows. Create the directory if it doesn't exist. The mongo Shell doesn't create new directories.

  4. In the OS command shell (not the MongoDB Shell), use the following command to connect to MongoDB on default port 27017. Replace <data_directory_path> with the directory chosen in the previous step.

    Console
    mongod --dbpath <data_directory_path>
    

Use the previously installed MongoDB Shell in the following steps to create a database, make collections, and store documents. For more information on MongoDB Shell commands, see mongosh.

  1. Open a MongoDB command shell instance by launching mongosh.exe.

  2. In the command shell connect to the default test database by running the following command:

    Console
    mongosh
    
  3. Run the following command in the command shell:

    Console
    use BookStore
    

    A database named BookStore is created if it doesn't already exist. If the database does exist, its connection is opened for transactions.

  4. Create a Books collection using following command:

    Console
    db.createCollection('Books')
    

    The following result is displayed:

    Console
    { "ok" : 1 }
    
  5. Define a schema for the Books collection and insert two documents using the following command:

    Console
    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" }])
    

    A result similar to the following is displayed:

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

    Note

    The ObjectIds shown in the preceding result won't match those shown in the command shell.

  6. View the documents in the database using the following command:

    Console
    db.Books.find().pretty()
    

    A result similar to the following is displayed:

    Console
    {
         "_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"
     }
    

    The schema adds an autogenerated _id property of type ObjectId for each document.

Create the ASP.NET Core web API project

  1. Go to File > New > Project.

  2. Select the ASP.NET Core Web API project type, and select Next.

  3. Name the project BookStoreApi, and select Next.

  4. Select the .NET 6.0 (Long-term support) framework and select Create.

  5. In the Package Manager Console window, navigate to the project root. Run the following command to install the .NET driver for MongoDB:

    PowerShell
    Install-Package MongoDB.Driver
    

Add an entity model

  1. Add a Models directory to the project root.

  2. Add a Book class to the Models directory with the following code:

    C#
    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!;
    }
    

    In the preceding class, the Id property is:

    • Required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
    • Annotated with [BsonId] to make this property the document's primary key.
    • Annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.

    The BookName property is annotated with the [BsonElement] attribute. The attribute's value of Name represents the property name in the MongoDB collection.

Add a configuration model

  1. Add the following database configuration values to appsettings.json:

    JSON
    {
        "BookStoreDatabase": {
            "ConnectionString": "mongodb://localhost:27017",
            "DatabaseName": "BookStore",
            "BooksCollectionName": "Books"
        },
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.AspNetCore": "Warning"
            }
        },
        "AllowedHosts": "*"
    }
    
  2. Add a BookStoreDatabaseSettings class to the Models directory with the following code:

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

    The preceding BookStoreDatabaseSettings class is used to store the appsettings.json file's BookStoreDatabase property values. The JSON and C# property names are named identically to ease the mapping process.

  3. Add the following highlighted code to Program.cs:

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

    In the preceding code, the configuration instance to which the appsettings.json file's BookStoreDatabase section binds is registered in the Dependency Injection (DI) container. For example, the BookStoreDatabaseSettings object's ConnectionString property is populated with the BookStoreDatabase:ConnectionString property in appsettings.json.

  4. Add the following code to the top of Program.cs to resolve the BookStoreDatabaseSettings reference:

    C#
    using BookStoreApi.Models;
    

Add a CRUD operations service

  1. Add a Services directory to the project root.

  2. Add a BooksService class to the Services directory with the following code:

    C#
    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);
    }
    

    In the preceding code, a BookStoreDatabaseSettings instance is retrieved from DI via constructor injection. This technique provides access to the appsettings.json configuration values that were added in the Add a configuration model section.

  3. Add the following highlighted code to Program.cs:

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

    In the preceding code, the BooksService class is registered with DI to support constructor injection in consuming classes. The singleton service lifetime is most appropriate because BooksService takes a direct dependency on MongoClient. Per the official Mongo Client reuse guidelines, MongoClient should be registered in DI with a singleton service lifetime.

  4. Add the following code to the top of Program.cs to resolve the BooksService reference:

    C#
    using BookStoreApi.Services;
    

The BooksService class uses the following MongoDB.Driver members to run CRUD operations against the database:

  • MongoClient: Reads the server instance for running database operations. The constructor of this class is provided the MongoDB connection string:

    C#
    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: Represents the Mongo database for running operations. This tutorial uses the generic GetCollection<TDocument>(collection) method on the interface to gain access to data in a specific collection. Run CRUD operations against the collection after this method is called. In the GetCollection<TDocument>(collection) method call:

    • collection represents the collection name.
    • TDocument represents the CLR object type stored in the collection.

GetCollection<TDocument>(collection) returns a MongoCollection object representing the collection. In this tutorial, the following methods are invoked on the collection:

  • DeleteOneAsync: Deletes a single document matching the provided search criteria.
  • Find<TDocument>: Returns all documents in the collection matching the provided search criteria.
  • InsertOneAsync: Inserts the provided object as a new document in the collection.
  • ReplaceOneAsync: Replaces the single document matching the provided search criteria with the provided object.

Add a controller

Add a BooksController class to the Controllers directory with the following code:

C#
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();
    }
}

The preceding web API controller:

  • Uses the BooksService class to run CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.
  • Calls CreatedAtAction in the Create action method to return an HTTP 201 response. Status code 201 is the standard response for an HTTP POST method that creates a new resource on the server. CreatedAtAction also adds a Location header to the response. The Location header specifies the URI of the newly created book.

Test the web API

  1. Build and run the app.

  2. Navigate to https://localhost:<port>/api/books, where <port> is the automatically assigned port number for the app, to test the controller's parameterless Get action method. A JSON response similar to the following is displayed:

    JSON
    [
      {
        "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. Navigate to https://localhost:<port>/api/books/{id here} to test the controller's overloaded Get action method. A JSON response similar to the following is displayed:

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

Configure JSON serialization options

There are two details to change about the JSON responses returned in the Test the web API section:

  • The property names' default camel casing should be changed to match the Pascal casing of the CLR object's property names.
  • The bookName property should be returned as Name.

To satisfy the preceding requirements, make the following changes:

  1. In Program.cs, chain the following highlighted code on to the AddControllers method call:

    C#
    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);
    

    With the preceding change, property names in the web API's serialized JSON response match their corresponding property names in the CLR object type. For example, the Book class's Author property serializes as Author instead of author.

  2. In Models/Book.cs, annotate the BookName property with the [JsonPropertyName] attribute:

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

    The [JsonPropertyName] attribute's value of Name represents the property name in the web API's serialized JSON response.

  3. Add the following code to the top of Models/Book.cs to resolve the [JsonProperty] attribute reference:

    C#
    using System.Text.Json.Serialization;
    
  4. Repeat the steps defined in the Test the web API section. Notice the difference in JSON property names.

Add authentication support to a web API

ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following:

Duende Identity Server is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Duende Identity Server enables the following security features:

  • Authentication as a Service (AaaS)
  • Single sign-on/off (SSO) over multiple application types
  • Access control for APIs
  • Federation Gateway

Important

Duende Software might require you to pay a license fee for production use of Duende Identity Server. For more information, see Migrate from ASP.NET Core 5.0 to 6.0.

For more information, see the Duende Identity Server documentation (Duende Software website).

Additional resources

This tutorial creates a web API that runs Create, Read, Update, and Delete (CRUD) operations on a MongoDB NoSQL database.

In this tutorial, you learn how to:

  • Configure MongoDB
  • Create a MongoDB database
  • Define a MongoDB collection and schema
  • Perform MongoDB CRUD operations from a web API
  • Customize JSON serialization

View or download sample code (how to download)

Prerequisites

Configure MongoDB

If using Windows, MongoDB is installed at C:\Program Files\MongoDB by default. Add C:\Program Files\MongoDB\Server\<version_number>\bin to the Path environment variable. This change enables MongoDB access from anywhere on your development machine.

Use the mongo Shell in the following steps to create a database, make collections, and store documents. For more information on mongo Shell commands, see Working with the mongo Shell.

  1. Choose a directory on your development machine for storing the data. For example, C:\BooksData on Windows. Create the directory if it doesn't exist. The mongo Shell doesn't create new directories.

  2. Open a command shell. Run the following command to connect to MongoDB on default port 27017. Remember to replace <data_directory_path> with the directory you chose in the previous step.

    Console
    mongod --dbpath <data_directory_path>
    
  3. Open another command shell instance. Connect to the default test database by running the following command:

    Console
    mongo
    
  4. Run the following command in a command shell:

    Console
    use BookstoreDb
    

    A database named BookstoreDb is created if it doesn't already exist. If the database does exist, its connection is opened for transactions.

  5. Create a Books collection using following command:

    Console
    db.createCollection('Books')
    

    The following result is displayed:

    Console
    { "ok" : 1 }
    
  6. Define a schema for the Books collection and insert two documents using the following command:

    Console
    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'}])
    

    The following result is displayed:

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

    Note

    The ID's shown in this article will not match the IDs when you run this sample.

  7. View the documents in the database using the following command:

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

    The following result is displayed:

    Console
    {
      "_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"
    }
    

    The schema adds an autogenerated _id property of type ObjectId for each document.

The database is ready. You can start creating the ASP.NET Core web API.

Create the ASP.NET Core web API project

  1. Go to File > New > Project.

  2. Select the ASP.NET Core Web Application project type, and select Next.

  3. Name the project BooksApi, and select Create.

  4. Select the .NET Core target framework and ASP.NET Core 3.0. Select the API project template, and select Create.

  5. Visit the NuGet Gallery: MongoDB.Driver to determine the latest stable version of the .NET driver for MongoDB. In the Package Manager Console window, navigate to the project root. Run the following command to install the .NET driver for MongoDB:

    PowerShell
    Install-Package MongoDB.Driver -Version {VERSION}
    

Add an entity model

  1. Add a Models directory to the project root.

  2. Add a Book class to the Models directory with the following code:

    C#
    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; }
        }
    }
    

    In the preceding class, the Id property is:

    • Required for mapping the Common Language Runtime (CLR) object to the MongoDB collection.
    • Annotated with [BsonId] to make this property the document's primary key.
    • Annotated with [BsonRepresentation(BsonType.ObjectId)] to allow passing the parameter as type string instead of an ObjectId structure. Mongo handles the conversion from string to ObjectId.

    The BookName property is annotated with the [BsonElement] attribute. The attribute's value of Name represents the property name in the MongoDB collection.

Add a configuration model

  1. Add the following database configuration values to appsettings.json:

    JSON
    {
      "BookstoreDatabaseSettings": {
        "BooksCollectionName": "Books",
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookstoreDb"
      },
      "Logging": {
        "IncludeScopes": false,
        "Debug": {
          "LogLevel": {
            "Default": "Warning"
          }
        },
        "Console": {
          "LogLevel": {
            "Default": "Warning"
          }
        }
      }
    }
    
  2. Add a BookstoreDatabaseSettings.cs file to the Models directory with the following code:

    C#
    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; }
        }
    }
    

    The preceding BookstoreDatabaseSettings class is used to store the appsettings.json file's BookstoreDatabaseSettings property values. The JSON and C# property names are named identically to ease the mapping process.

  3. Add the following highlighted code to Startup.ConfigureServices:

    C#
    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();
    }
    

    In the preceding code:

    • The configuration instance to which the appsettings.json file's BookstoreDatabaseSettings section binds is registered in the Dependency Injection (DI) container. For example, a BookstoreDatabaseSettings object's ConnectionString property is populated with the BookstoreDatabaseSettings:ConnectionString property in appsettings.json.
    • The IBookstoreDatabaseSettings interface is registered in DI with a singleton service lifetime. When injected, the interface instance resolves to a BookstoreDatabaseSettings object.
  4. Add the following code to the top of Startup.cs to resolve the BookstoreDatabaseSettings and IBookstoreDatabaseSettings references:

    C#
    using BooksApi.Models;
    

Add a CRUD operations service

  1. Add a Services directory to the project root.

  2. Add a BookService class to the Services directory with the following code:

    C#
    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);
        }
    }
    

    In the preceding code, an IBookstoreDatabaseSettings instance is retrieved from DI via constructor injection. This technique provides access to the appsettings.json configuration values that were added in the Add a configuration model section.

  3. Add the following highlighted code to Startup.ConfigureServices:

    C#
    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();
    }
    

    In the preceding code, the BookService class is registered with DI to support constructor injection in consuming classes. The singleton service lifetime is most appropriate because BookService takes a direct dependency on MongoClient. Per the official Mongo Client reuse guidelines, MongoClient should be registered in DI with a singleton service lifetime.

  4. Add the following code to the top of Startup.cs to resolve the BookService reference:

    C#
    using BooksApi.Services;
    

The BookService class uses the following MongoDB.Driver members to run CRUD operations against the database:

  • MongoClient: Reads the server instance for running database operations. The constructor of this class is provided the MongoDB connection string:

    C#
    public BookService(IBookstoreDatabaseSettings settings)
    {
        var client = new MongoClient(settings.ConnectionString);
        var database = client.GetDatabase(settings.DatabaseName);
    
        _books = database.GetCollection<Book>(settings.BooksCollectionName);
    }
    
  • IMongoDatabase: Represents the Mongo database for running operations. This tutorial uses the generic GetCollection<TDocument>(collection) method on the interface to gain access to data in a specific collection. Run CRUD operations against the collection after this method is called. In the GetCollection<TDocument>(collection) method call:

    • collection represents the collection name.
    • TDocument represents the CLR object type stored in the collection.

GetCollection<TDocument>(collection) returns a MongoCollection object representing the collection. In this tutorial, the following methods are invoked on the collection:

  • DeleteOne: Deletes a single document matching the provided search criteria.
  • Find<TDocument>: Returns all documents in the collection matching the provided search criteria.
  • InsertOne: Inserts the provided object as a new document in the collection.
  • ReplaceOne: Replaces the single document matching the provided search criteria with the provided object.

Add a controller

Add a BooksController class to the Controllers directory with the following code:

C#
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();
        }
    }
}

The preceding web API controller:

  • Uses the BookService class to run CRUD operations.
  • Contains action methods to support GET, POST, PUT, and DELETE HTTP requests.
  • Calls CreatedAtRoute in the Create action method to return an HTTP 201 response. Status code 201 is the standard response for an HTTP POST method that creates a new resource on the server. CreatedAtRoute also adds a Location header to the response. The Location header specifies the URI of the newly created book.

Test the web API

  1. Build and run the app.

  2. Navigate to https://localhost:<port>/api/books to test the controller's parameterless Get action method. The following JSON response is displayed:

    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. Navigate to https://localhost:<port>/api/books/{id here} to test the controller's overloaded Get action method. The following JSON response is displayed:

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

Configure JSON serialization options

There are two details to change about the JSON responses returned in the Test the web API section:

  • The property names' default camel casing should be changed to match the Pascal casing of the CLR object's property names.
  • The bookName property should be returned as Name.

To satisfy the preceding requirements, make the following changes:

  1. Json.NET has been removed from ASP.NET shared framework. Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  2. In Startup.ConfigureServices, chain the following highlighted code on to the AddControllers method call:

    C#
    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());
    }
    

    With the preceding change, property names in the web API's serialized JSON response match their corresponding property names in the CLR object type. For example, the Book class's Author property serializes as Author.

  3. In Models/Book.cs, annotate the BookName property with the following [JsonProperty] attribute:

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

    The [JsonProperty] attribute's value of Name represents the property name in the web API's serialized JSON response.

  4. Add the following code to the top of Models/Book.cs to resolve the [JsonProperty] attribute reference:

    C#
    using Newtonsoft.Json;
    
  5. Repeat the steps defined in the Test the web API section. Notice the difference in JSON property names.

Add authentication support to a web API

ASP.NET Core Identity adds user interface (UI) login functionality to ASP.NET Core web apps. To secure web APIs and SPAs, use one of the following:

Duende IdentityServer is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core. Duende IdentityServer enables the following security features:

  • Authentication as a Service (AaaS)
  • Single sign-on/off (SSO) over multiple application types
  • Access control for APIs
  • Federation Gateway

For more information, see Overview of Duende IdentityServer.

For more information on other authentication providers, see Community OSS authentication options for ASP.NET Core

Next steps

For more information on building ASP.NET Core web APIs, see the following resources: