This tutorial creates a web API that performs Create, Read, Update, and Delete (CRUD) operations on a MongoDB NoSQL database.
In this tutorial, you learn how to:
How to Install MongoDB 4.x on MacOS with Homebrew
Open another command shell instance. Connect to the default test database by running the following command:
mongo
Run the following in a command shell:
use BookstoreDb
If it doesn’t already exist, a database named BookstoreDb is created. If the database does exist, its connection is opened for transactions.
Create a Books
collection using following command:
db.createCollection('Books')
The following result is displayed:
{ "ok" : 1 }
Define a schema for the Books
collection and insert two documents using the following command:
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:
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5bfd996f7b8e48dc15ff215d"),
ObjectId("5bfd996f7b8e48dc15ff215e")
]
}
View the documents in the database using the following command:
db.Books.find({}).pretty()
The following result is displayed:
{
"_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 database is ready. You can start creating the ASP.NET Core web API.
Go to File > New > Project.
Select the ASP.NET Core Web Application project type, and select Next.
Name the project BooksApi, and select Create.
Select the .NET Core target framework and ASP.NET Core 3.0. Select the API project template, and select Create.
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:
Install-Package MongoDB.Driver -Version {VERSION}
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; }
}
}
Add the following database configuration values to appsettings.json:
{
"BookstoreDatabaseSettings": {
"BooksCollectionName": "Books",
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookstoreDb"
},
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
Add a BookstoreDatabaseSettings.cs
file to the Models directory with the following code:
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.
Add the following highlighted code to 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();
}
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.
Add the following code to the top of Startup.cs to resolve the BookstoreDatabaseSettings
and IBookstoreDatabaseSettings
references:
using BooksApi.Models;
Add a Services directory to the project root.
Add a BookService class to the Services directory with the following code:
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.
Add the following highlighted code to 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();
}
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.
Add the following code to the top of Startup.cs
to resolve the BookService reference:
using BooksApi.Services;
The BookService class uses the following MongoDB.Driver members to perform CRUD operations against the database:
MongoClient – Reads the server instance for performing database operations. The constructor of this class is provided the MongoDB connection string:
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 performing operations. This tutorial uses the generic GetCollection(collection) method on the interface to gain access to data in a specific collection. Perform 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 BooksController
class to the Controllers directory with the following code:
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(book.Id);
return NoContent();
}
}
}
The preceding web API controller:
http://localhost:<port>/api/books
to test the controller’s parameterless Get action method. The following JSON response is displayed:[
{
"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"
}
]
Navigate to http://localhost:<port>/api/books/{id here}
to test the controller’s overloaded Get action method. The following JSON response is displayed:
{
"id":"{ID}",
"bookName":"Clean Code",
"price":43.15,
"category":"Computers",
"author":"Robert C. Martin"
}
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:
JSON.NET has been removed from ASP.NET shared framework. Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson
.
In Startup.ConfigureServices
, chain the following highlighted code on to the AddMvc method call:
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.
In Models/Book.cs
, annotate the BookName property with the following [JsonProperty] attribute:
[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.
Add the following code to the top of Models/Book.cs
to resolve the [JsonProperty] attribute reference:
using Newtonsoft.Json;
Repeat the steps defined in the Test the web API section. Notice the difference in JSON property names.
#aspnet #mongodb #aspnet-core #webapi