How to create a book store web API with Asp.Net Core
Tags: webdev, programming, tutorial, api
Published: 2022-11-28 16:03:14

## Getting started

Creating a dotnet web API run : `dotnet new webapi -o BookStoreApi` in your terminal, after that, then `cd BookStoreApi`. With the help of these commands, a fresh ASP.NET Core Web API project is created, which is then opened in Visual Studio Code. 

`dotnet new webapi -o BookStoreApi`
![dotnet new webapi -o BookStoreApi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mq499q44v3txlldl1qm.png)

`cd BookStoreApi`

![cd BookStoreApi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k32qtwaw2dl3fqisfpew.png)

As soon as the Omnisharp servers launch, a dialog asks **Required assets to build and debug are missing from 'BookStoreApi'. Add them?**. Select **Yes**. As displayed in the screenshot below:


![Dialog box screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/551wu7x3zyqnnrmv5egb.png)


## Add an entity model

- The project root should now have a Models directory.

- Now, add a `Book` class to the Models directory with the following code:

```
using System.Text.Json.Serialization;
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")]
		[JsonPropertyName("Name")]
		public string BookName { get; set; } = null!;
		
		public decimal Price{ get; set; } 
		
		public string Category{ get; set; } = null!;

		public string Author { get; set; } = null!;
	}
}
```
**Detailed explanation of the above code example:**

- The `Id` property is required for mapping the CLR(Common Language Runtime) object to the MongoDB collection.

- It annotate with `[BsonId]` to make this property the document's primary key.

- It also, annotate 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.

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

## How can I configure the model?
To configure the database model, add a database configuration settings class to the model by just creating a class in the **Models** folder or directory and name it `BookStoreDatabaseSettings` with the extension `.cs`. 

Now, copy and paste the code below in the `BookStoreDatabaseSettings.cs` file:

```
namespace BookStoreApi.Models;

public class BookStoreDatabaseSettings
{
    public string ConnectionString { get; set; } = null!;

    public string DatabaseName { get; set; } = null!;

    public string BooksCollectionName { get; set; } = null!;
}
```

**Detailed explanation of the above code example:**
The values for the `BookStoreDatabase` field in the `appsettings.json` file are stored in the 'BookStoreDatabaseSettings' class.To make the mapping process easier, the property names in both JSON and C# are same.

## Let's add a controller
Now, let's add a controller class named `BooksController` to the **Controllers** folder or directory. After that, add the following code:

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

**Detailed explanation of the above code example:**

- The controller performs **CRUD** operations using the "BooksService" class.

- Action methods for supporting HTTP GET, POST, PUT, and DELETE requests are provided by the controller.

- Returns an HTTP 201 response when [CreatedAtAction](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.createdataction?view=aspnetcore-7.0) is called in the 'Create' action method. The typical answer to an HTTP POST method that adds a new resource to the server is status code 201. Additionally, the response's "Location" header is added by "CreatedAtAction." The URL of the freshly generated book is specified in the 'Location' header.

## Incorporate a CRUD operations service.
To the project root, add a **Services** directory. After that, add the following code to the **Services** directory to create a class named `BooksService`:

```
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);
}
```
**Detailed explanation of the above code example:**

The following `MongoDB.Driver` members are used by the `BooksService` class to perform CRUD operations on the database.

- `MongoClient`: Carries out database operations by reading the server instance.

- `IMongoDatabase`: serves as a running representation of the Mongo database. In this article, we obtain data in a particular collection using the interface's general `GetCollection<TDocument>(collection)` method. Use the collection for CRUD operations after calling this function. In the `GetCollection<TDocument>(collection)` method call:
   `collection`: represents the collection name.
   `TDocument`: represents the CLR(Common Language Runtime) object type stored in the collection.

`GetCollection<TDocument>(collection)` returns a [MongoCollection](https://mongodb.github.io/mongo-csharp-driver/2.14/apidocs/html/T_MongoDB_Driver_MongoCollection.htm) object representing the collection. In this article, the following methods are invoked on the collection:
   `DeleteOneAsync`: Removes one document that matches the given search criteria.
`Find<TDocument>`: Returns all collection documents that meet the specified search criteria.
`InsertOneAsync`: Adds the supplied objects to the collection as a new document.
`ReplaceOneAsync`: Replaces the specified object with the solitary document that matches the provided search parameters.

You will find several problems in your code as you write it. To fix this issue, update the following files:

## Update `Program.cs` 
Now, update the `Program.cs` file with the code below:

```
using BookStoreApi.Models;
using BookStoreApi.Services;

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);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// builder.Services.AddEndpointsApiExplorer();
// builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
	// app.UseSwagger();
	// app.UseSwaggerUI();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
```

You'll see that the "Program.cs" file has some new code in it. Let's now discuss the functions of the codes:


```
.AddJsonOptions(
        options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
```
After the aforementioned modification, the property names in the serialized JSON response from the web API match those in the CLR (Common Language Runtime) object type. For instance, the `Author` property of the `Boo` class serializes as `Author` rather than `author`.

```
builder.Services.AddSingleton<BooksService>();
```
The `BooksService` class is registered with Dependency Injection (DI) in the above code to facilitate constructor injection in consuming classes. Because `BooksService` directly depends on `MongoClient`, the singleton service lifespan is the most suitable. The `MongoClient` object should be registered in DI(Dependency Injection) with a singleton service lifetime in accordance with the standard [Mongo Client reuse guidelines](https://mongodb.github.io/mongo-csharp-driver/2.14/reference/driver/connecting/#re-use).

The `BooksService` class in this article uses the `MongoDB.Driver` components to perform CRUD operations on the database.

```
builder.Services.Configure<BookStoreDatabaseSettings>(
    builder.Configuration.GetSection("BookStoreDatabase"));
```
The configuration instance that the `appsettings.json` file's `BookStoreDatabase` section binds is registered in the Dependency Injection (DI) container in the code that comes before it. For instance, the `BookStoreDatabase:ConnectionString` field of the `appsettings.json` object is used to populate the `ConnectionString` property of the `BookStoreDatabaseSettings` object.

## Update `appsettings.json`
To the file `appsettings.json`, insert the following code:

```
"BookStoreDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookStore",
        "BooksCollectionName": "Books"
    },
```

**Updated `appsettings.json` file code example:**

```
{
  "BookStoreDatabase": {
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "BookStore",
    "BooksCollectionName": "Books"
},
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
```

We have completed the implementations at this time. Press **Ctrl + F5** on your keyboard or type `dotnet run` in your terminal to launch the web API. 


[Would you like the source code? If yes, click this link.](https://github.com/Folasayo-Samuel/BookStoreApi/tree/main/Services)

[Also interested in learning more about the Asp.Net Core Web API? Click this link.](https://dev.to/folasayosamuel/what-is-the-idea-behind-the-aspnet-core-web-api-4d1g)

Thanks for reading...

**Happy Coding!**