Xây dựng RESTful APIs với ASP.NET Core và MongoDB

Kiến thức cần biết:

  • Biết lập trình C#
  • Biết lập trình ASP.NET Core
  • Biết cơ bản về RESTful API
  • Cài đặt môi trường phát triển .NET 6 hoặc các phiên bản sau của Microsoft
  • Làm việc với CSDL MongoDB

Tạo dự án với .NET CLI

  • Chạy lệnh sau để tạo dự án:
    dotnet new webapi -o MyStoreApi
  • Chuyển lệnh đến thư mục MyStoreApi và thêm trình điều khiển Cơ sở dữ liệu MongoDB vào dự án với lệnh sau:
    cd ./MyStoreApi
    dotnet add package MongoDB.Driver

Thêm mô hình thực thể

  • Tạo thư mục "Models" trong thư mục gốc của dự án
  • Thêm lớp "Product" vào trong thư mục Models và thực hiện mã lệnh sau:
    using MongoDB.Bson;
    using MongoDB.Bson.Serialization.Attributes;
    
    namespace MyStoreApi.Models;
    
    public class Product
    {
      [BsonId]
      [BsonRepresentation(BsonType.ObjectId)]
      public string? Id { set; get; }
    
      [BsonElement("Name")]
      public string ProductName { get; set; } = null!;
    
      public decimal Price { get; set; }
    
      public string Category { get; set; } = null!;
    
      public string Description { get; set; } = null!;
    
    }

    Trong định nghĩa lớp trên thuộc tính Id có nghĩa:

    • Thuộc tính Id cần thiết để ánh xạ đối tượng Common Language Runtime (CLR) vào CSDL MongoDB.
    • Chú thích [BsonId] để đặt thuộc tính này thành khóa chính của tài liệu.
    • Chú thích bằng [BsonRepresentation (BsonType.ObjectId)] để cho phép truyền tham số dưới dạng chuỗi kiểu thay vì cấu trúc ObjectId. Mongo xử lý việc chuyển đổi từ chuỗi thành ObjectId.

Thêm cấu hình mô hình với CSDL MongoDB

  • Thêm cấu hình CSDL trong tệp appsetting.json:
    {
      "MyStoreDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "MyStore",
        "ProductsCollectionName": "Products"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
  • Thêm lớp MyStoreDbSettings vào thư mục Models và thực hiện mã lệnh sau:
    namespace MyStoreApi.Models;
    
    public class MyStoreDbSettings
    {
        public string ConnectionString { get; set; } = null!;
    
        public string DatabaseName { get; set; } = null!;
    
        public string ProductsCollectionName { get; set; } = null!;
    }

    Lớp MyStoreDbSettings được sử dụng để lưu trữ các giá trị thuộc tính MyStoreDatabase của tệp appsettings.json. Tên thuộc tính JSON và C# được đặt tên giống nhau để dễ dàng quá trình ánh xạ.

  • Thêm mã lệnh sau vào tệp Program.cs:
    using MyStoreApi.Models;
    using MyStoreApi.Services;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<MyStoreDbSettings>(
        builder.Configuration.GetSection("MyStoreDatabase"));

    Trong mã lệnh trên, phiên bản cấu hình mà phần MyStoreDatabase của tệp appsettings.json liên kết được đăng ký trong vùng chứa "chèn phụ thuộc" (Dependency Injection - DI). Ví dụ: thuộc tính ConnectionString của đối tượng MyStoreDbSettings được điền với thuộc tính MyStoreDatabase: ConnectionString trong appsettings.json.

Thêm các thao tác CRUD

  • Thêm thư mục Services vào trong thư mục gốc của dự án
  • Thêm lớp ProductService vào thư mục Services và thực hiện mã lệnh sau:
    using MyStoreApi.Models;
    using MyStoreApi.Services;
    using Microsoft.Extensions.Options;
    using MongoDB.Driver;
    using MongoDB.Bson;
    
    namespace MyStoreApi.Services;
    
    public class ProductsService
    {
        private readonly IMongoCollection<Product> _productsCollection;
    
        public ProductsService(
            IOptions<MyStoreDbSettings> myStoreDbSettings)
        {
            var mongoClient = new MongoClient(
                myStoreDbSettings.Value.ConnectionString);
    
            var mongoDatabase = mongoClient.GetDatabase(
                myStoreDbSettings.Value.DatabaseName);
    
            _productsCollection = mongoDatabase.GetCollection<Product>(
                myStoreDbSettings.Value.ProductsCollectionName);
        }
    
        public async Task<List<Product>> GetAsync() =>
            await _productsCollection.Find(_ => true).ToListAsync();
    
        public async Task<Product?> GetAsync(string id) =>
            await _productsCollection.Find(x => x.Id == id).FirstOrDefaultAsync();
    
        public async Task CreateAsync(Product newProduct) =>
            await _productsCollection.InsertOneAsync(newProduct);
    
        public async Task UpdateAsync(string id, Product updatedProduct) =>
            await _productsCollection.ReplaceOneAsync(x => x.Id == id, updatedProduct);
    
        public async Task RemoveAsync(string id) =>
            await _productsCollection.DeleteOneAsync(x => x.Id == id);
    }

    Trong đoạn mã lệnh trên, một đối tượng MyStoreDbSettings được truy xuất từ "chèn phụ thuộc" (Dependency Injection - DI) thông qua phương thức chèn hàm khởi tạo. Kỹ thuật này cung cấp quyền truy cập vào các giá trị cấu hình appsettings.json đã được thêm vào phần Thêm mô hình cấu hình với CSDL MongoDB.

  • Thêm mã lệnh sau vào tệp Program.cs
    using MyStoreApi.Models;
    using MyStoreApi.Services;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.Configure<MyStoreDbSettings>(
        builder.Configuration.GetSection("MyStoreDatabase"));
    builder.Services.AddSingleton<ProductsService>()

Thêm Controller

Thêm lớp ProductsController vào thư mục Controllers và thực hiện mã lệnh sau:

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

namespace MyStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
  private readonly ProductsService _productsService;

  public ProductsController(ProductsService productsService) =>
      _productsService = productsService;

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

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

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

    return product;
  }

  [HttpPost]
  public async Task<IActionResult> Post(Product newProduct)
  {
    await _productsService.CreateAsync(newProduct);

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

  [HttpPut("{id:length(24)}")]
  public async Task<IActionResult> Update(string id, Product updatedProduct)
  {
    var product = await _productsService.GetAsync(id);

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

    updatedProduct.Id = product.Id;

    await _productsService.UpdateAsync(id, updatedProduct);

    return NoContent();
  }

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

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

    await _productsService.RemoveAsync(product.Id ?? "");

    return NoContent();
  }
}

Trong Controller của Web API trên:

  • Sử dụng lớp ProductsService để thực hiện các thao tác CRUD.
  • Chứa các phương thức hành động để hỗ trợ các yêu cầu HTTP GET, POST, PUTDELETE.
  • Gọi phương thức CreatedAtAction() trong phương thức public async Task<IActionResult> Post(Product newProduct) để trả về phản hồi (response) HTTP 201. Mã trạng thái 201 là phản hồi tiêu chuẩn cho phương thức HTTP POST tạo tài nguyên mới trên máy chủ. CreatedAtAction() cũng thêm tiêu đề Vị trí (Location) vào phản hồi (response). Tiêu đề Vị trí (Location) chỉ định URI của sản phẩm mới được tạo.

Kiểm thử Web API

Để kiểm thử Web API thực hiện các bước như sau:

  • Chạy Web API với lệnh sau:
    $ dotnet run
    ...
    Building...
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://localhost:7171
    ...

    Lưu ý: khi thực chạy lệnh trên cho chúng ta biết Web API được biên dịch và chạy tại đường dẫn https://localhost:7171 (trên cổng 7171).

  • Kiểm thử Web API với Swagger: vào trình duyệt với đường dẫn sau https://localhost:7171/swagger