VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
ASP.NET Core Web APICấu trúc ứng dụng
Bài 9/19
5 phút

Dependency injection trong ASP.NET Core

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Ở chương trước, danh sách sản phẩm là một list static nằm ngay trong controller. Controller vừa xử lý request vừa giữ dữ liệu, nên muốn đổi sang database thì phải sửa controller. Bài này tách phần lưu trữ ra một service, rồi để ASP.NET Core tự đưa service đó vào controller.

Khái niệm

🧰 DI container: thành phần của ASP.NET Core tự tạo object và truyền vào constructor của class cần nó.

⏱️ Service lifetime: quy định container tạo object mới khi nào.

Đăng ký bằng Container tạo object
AddTransient mới mỗi lần có nơi cần
AddScoped một object cho mỗi request
AddSingleton một object cho cả ứng dụng

Ví dụ

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<
    IProductStore, InMemoryProductStore>();

var app = builder.Build();
app.MapControllers();
app.Run();

public interface IProductStore
{
    List<string> GetAll();
}

public class InMemoryProductStore : IProductStore
{
    private readonly List<string> _names =
        new List<string> { "Pen", "Notebook" };

    public List<string> GetAll() => _names;
}

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly IProductStore _store;

    public ProductsController(IProductStore store)
    {
        _store = store;
    }

    [HttpGet]
    public List<string> GetAll() => _store.GetAll();
}
  • AddSingleton<IProductStore, InMemoryProductStore>(): nơi nào cần IProductStore thì nhận InMemoryProductStore.
  • Controller chỉ khai báo tham số IProductStore trong constructor. Container tự tạo object và truyền vào. Ở bài DIP khoá OOP bạn tự tạo object rồi truyền vào constructor, ở đây container làm việc đó.
  • Dữ liệu trong bộ nhớ phải sống suốt ứng dụng, nên đăng ký Singleton.
  • Sang chương 4, controller vẫn nhận IProductStore qua constructor như cũ, còn phần đọc ghi database nằm trong một class store mới.
tạo một lần, dùng lại truyền vào constructor GET /api/products DI container InMemoryProductStore ProductsController
Container tạo store rồi đưa vào constructor controller

Thử ngay

Thêm class đếm và một controller mới vào project, rồi đăng ký bằng AddScoped:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<Counter>();

var app = builder.Build();
app.MapControllers();
app.Run();

public class Counter
{
    private int _count;

    public int Next()
    {
        _count++;
        return _count;
    }
}

[ApiController]
[Route("api/count")]
public class CountController : ControllerBase
{
    [HttpGet]
    public int Get([FromServices] Counter counter) =>
        counter.Next();
}

Gọi curl -i http://localhost:5000/api/count ba lần. Sau đó đổi AddScoped thành AddSingleton, chạy lại server và gọi thêm ba lần.

Đoán trước khi chạy: với AddScoped, ba lần gọi trả về những số nào?

Xem kết quả
Status 200 cả sáu lần, body lần lượt là:
AddScoped:    1, 1, 1
AddSingleton: 1, 2, 3

Scoped tạo Counter mới cho mỗi request, nên lần nào cũng bắt đầu lại từ 0. Singleton dùng chung một Counter cho cả ứng dụng, nên số tăng dần. [FromServices] bảo ASP.NET Core lấy tham số này từ container.

Lỗi hay gặp

Quên đăng ký service. Request tới controller nhận lỗi 500, log báo không tạo được IProductStore.

// SAI — controller cần IProductStore mà chưa đăng ký
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();
// ĐÚNG
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<
    IProductStore, InMemoryProductStore>();

var app = builder.Build();
app.MapControllers();
app.Run();

Singleton phụ thuộc vào Scoped. Object sống suốt ứng dụng lại giữ một object vốn chỉ nên sống trong một request. Ở môi trường Development, ứng dụng dừng ngay lúc khởi động và báo lỗi này.

// SAI — Report sống mãi, lại giữ Counter của request
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<Counter>();
builder.Services.AddSingleton<Report>();
var app = builder.Build();   // báo lỗi tại đây

public class Report
{
    public Report(Counter counter) { }
}
// ĐÚNG — Report cũng chỉ sống trong một request
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<Counter>();
builder.Services.AddScoped<Report>();
var app = builder.Build();

public class Report
{
    public Report(Counter counter) { }
}

Tóm tắt

  • Đăng ký service trong Program.cs, nhận qua constructor của controller.
  • Transient tạo mới mỗi lần, Scoped mỗi request một, Singleton cả ứng dụng một.
  • Đăng ký bằng interface để đổi cách làm mà không sửa controller.
  • Service sống lâu không được phụ thuộc vào service sống ngắn hơn.

Tự kiểm tra

0/3 câu
Câu 1

Service lưu cấu hình đọc một lần lúc khởi động và dùng chung cho mọi request. Nên đăng ký bằng gì?

Câu 2

Gọi API thì nhận 500, log báo "Unable to resolve service for type 'IEmailSender'". Nguyên nhân?

Câu 3

Service đăng ký bằng AddScoped được dùng ở controller và ở một service khác trong CÙNG một request. Có bao nhiêu object được tạo?

CRUD hoàn chỉnhCấu hình với appsettings

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt