DEV Community

FOLASAYO SAMUEL OLAYEMI
FOLASAYO SAMUEL OLAYEMI

Posted on

How to create a book store web API with Asp.Net Core

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

cd BookStoreApi

cd BookStoreApi

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

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!;
    }
}
Enter fullscreen mode Exit fullscreen mode

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!;
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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 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);
}
Enter fullscreen mode Exit fullscreen mode

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 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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>();
Enter fullscreen mode Exit fullscreen mode

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.

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"));
Enter fullscreen mode Exit fullscreen mode

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"
    },
Enter fullscreen mode Exit fullscreen mode

Updated appsettings.json file code example:

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

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.

Also interested in learning more about the Asp.Net Core Web API? Click this link.

Thanks for reading...

Happy Coding!

Top comments (0)