Dependency Injection trong ASP.NET Core

ASP.NET Core được thiết kế lại từ đầu để hỗ trợ Dependency Injection. ASP.NET Core sẽ inject các đối tượng của các lớp phụ thuộc thông qua phương thức khởi tạo hoặc phương thức bằng cách sử dụng IoC container tích hợp.

Bạn có thể truy cập Hướng dẫn lập trình ASP.NET Core để không bỏ lỡ bất kỳ bài viết nào của chúng tôi.

IoC Container tích hợp

ASP.NET Core framework tích hợp sẵn IoC container đơn giản không có nhiều tính năng như các IoC container của bên thứ ba khác. Nếu bạn muốn có nhiều tính năng hơn như đăng ký tự động, quét, chặn hoặc trang trí thì bạn có thể thay thế IoC container tích hợp bằng container của bên thứ ba.

Mô hình Dependency Injection trong ASP.NET Core

Container tích hợp được thể hiện bằng cách triển khai thực hiện interface IServiceProvider để hỗ trợ injection phương thức khởi tạo theo mặc định. Các kiểu dữ liệu (lớp) được quản lý bởi IoC container tích hợp được gọi là các dịch vụ.

Về cơ bản có hai loại dịch vụ trong ASP.NET Core:

  1. Dịch vụ framework: Các dịch vụ là thành phần của ASP.NET Core framework như IApplicationBuilder, IHostingEnvironment, ILoggerFactory, v.v.
  2. Dịch vụ ứng dụng: Các dịch vụ (kiểu dữ liệu tùy chỉnh) mà bạn với tư cách là lập trình viên tạo cho ứng dụng của mình.

Để cho IoC container tự động inject các dịch vụ trong ứng dụng, trước tiên chúng ta cần đăng ký chúng với IoC container.

Đăng ký dịch vụ với IoC Container trong ASP.NET Core

Hãy xem xét ví dụ sau về interface ILog đơn giản và lớp triển khai thực hiện của nó là MyConsoleLogger. Chúng ta sẽ xem cách đăng ký nó với IoC container tích hợp và sử dụng nó trong ứng dụng của chúng ta.

public interface ILog
{
    void info(string str);
}

class MyConsoleLogger : ILog
{
    public void info(string str)
    {
        Console.WriteLine(str);
    }
}

ASP.NET Core cho phép chúng ta đăng ký các dịch vụ trong ứng dụng của mình với IoC container trong phương thức ConfigureServices của lớp Startup. Phương thức ConfigureServices có một tham số kiểu IServiceCollection được sử dụng để đăng ký các dịch vụ của ứng dụng.

Ví dụ dưới đây minh họa đăng ký interface ILog với IoC container trong phương thức ConfigureService():

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Add(new ServiceDescriptor(typeof(ILog), new MyConsoleLogger()));
    }

    // other code removed for clarity.. 
}

Như bạn đã thấy ở trên, phương thức Add() của interface IServiceCollection được sử dụng để đăng ký dịch vụ với IoC container. Các ServiceDescriptor được sử dụng để chỉ định một loại dịch vụ và thể hiện của nó.

Chúng tôi đã chỉ định ILog là loại dịch vụ và MyConsoleLogger là thể hiện của nó. Điều này sẽ đăng ký dịch vụ ILog như một singleton theo mặc định.

Bây giờ, IoC container sẽ tạo một đối tượng của lớp MyConsoleLogger ở bất cứ nơi nào chúng ta đưa ILog vào chẳng hạn như: phương thức khởi tạo hoặc bất kỳ phương thức trong toàn bộ ứng dụng có tham số là ILog.

Vì vậy, chúng ta có thể đăng ký các dịch vụ ứng dụng tùy chỉnh của mình với IoC container trong ứng dụng ASP.NET Core.

Ngoài cách đăng ký dịch vụ đã trình bày ở trên, ASP.NET Core có các phương thức mở rộng khác để đăng ký dịch vụ nhanh chóng và dễ dàng mà chúng ta sẽ thấy ở phần sau của hướng dẫn này.

Vòng đời của dịch vụ trong ASP.NET Core

IoC container tích hợp quản lý vòng đời của các dịch vụ đã đăng ký. Nó tự động xử lý một thể hiện của dịch vụ dựa trên vòng đời được chỉ định.

IoC container tích hợp hỗ trợ ba loại vòng đời:

  1. Singleton: IoC container sẽ tạo và chia sẻ một phiên bản dịch vụ duy nhất trong suốt vòng đời của ứng dụng.
  2. Transient: IoC container sẽ tạo một phiên bản mới của dịch vụ được chỉ định mỗi khi bạn yêu cầu.
  3. Scoped: IoC container sẽ tạo một phiên bản của loại dịch vụ được chỉ định một lần cho mỗi yêu cầu và sẽ được chia sẻ trong một yêu cầu.

Ví dụ sau đây cho thấy cách đăng ký một dịch vụ với các vòng đời khác nhau.

public void ConfigureServices(IServiceCollection services)
{
    // singleton
    services.Add(new ServiceDescriptor(typeof(ILog), new MyConsoleLogger()));
    
    // Transient
    services.Add(new ServiceDescriptor(typeof(ILog), typeof(MyConsoleLogger), ServiceLifetime.Transient));
    
    // Scoped
    services.Add(new ServiceDescriptor(typeof(ILog), typeof(MyConsoleLogger), ServiceLifetime.Scoped));
}

Phương thức đăng ký dịch vụ mở rộng

ASP.NET Core có các phương thức mở rộng để đăng ký cho từng loại vòng đời như: AddSingleton(), AddTransient()AddScoped() tương ứng cho vòng đời singleton, transient và scoped.

Ví dụ sau đây cho thấy cách đăng ký dịch vụ bằng các phương thức mở rộng.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ILog, MyConsoleLogger>();
    services.AddSingleton(typeof(ILog), typeof(MyConsoleLogger));

    services.AddTransient<ILog, MyConsoleLogger>();
    services.AddTransient(typeof(ILog), typeof(MyConsoleLogger));

    services.AddScoped<ILog, MyConsoleLogger>();
    services.AddScoped(typeof(ILog), typeof(MyConsoleLogger));
}

Injection phương thức khởi tạo trong ASP.NET Core

Khi chúng ta đăng ký một dịch vụ, IoC container sẽ tự động thực hiện injection cho phương thức khởi tạo nếu một loại dịch vụ được đưa vào làm tham số trong phương thức khởi tạo.

Ví dụ: chúng ta có thể sử dụng dịch vụ ILog trong bất kỳ MVC Controller nào. Hãy xem ví dụ sau.

public class HomeController : Controller
{
    private readonly ILog _log;

    public HomeController(ILog log)
    {
        _log = log;
    }
    
    public IActionResult Index()
    {
        _log.info("Executing /home/index");

        return View();
    }
}

Trong ví dụ trên, IoC container sẽ tự động truyền một thể hiện của MyConsoleLogger tới tham số log của phương thức khởi tạo của HomeController. Chúng ta không cần phải làm gì khác. IoC container sẽ tạo và hủy một thể hiện của ILog dựa trên vòng đời đã đăng ký.

Injection phương thức trong ASP.NET Core

Đôi khi chúng ta chỉ cần loại dịch vụ phụ thuộc trong một phương thức của Controller. Đối với trường hợp này, chúng ta sẽ sử dụng attribute [FromService] với tham số loại dịch vụ cần injection trong phương thức.

Ví dụ sau sẽ minh họa injection phương thức trong ASP.NET Core:

using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    public HomeController()
    {
    }

    public IActionResult Index([FromServices] ILog log)
    {
        log.info("Index method executing");

        return View();
    }
}

Injection thuộc tính trong ASP.NET Core

IoC container tích hợp không hỗ trợ injection cho các thuộc tính trong ASP.NET Core. Bạn sẽ phải sử dụng IoC container của bên thứ ba để thực hiện việc này.

Injection dịch vụ thủ công

Ngoài sử dụng injection phương thức khởi tạo và injection phương thức, chúng ta có thể truy cập thủ công các dịch vụ phụ thuộc đã đăng ký với IoC container tích hợp bằng cách sử dụng thuộc tính RequestServices của HttpContext.

Ví dụ sau sẽ minh họa cách sử dụng HttpContext để truy cập thủ công các dịch vụ phụ thuộc trong IoC container:

public class HomeController : Controller
{
    public HomeController()
    {
    }
    
    public IActionResult Index()
    {
        var services = this.HttpContext.RequestServices;
        var log = (ILog)services.GetService(typeof(ILog));
            
        log.info("Index method executing");
    
        return View();
    }
}

Tuy nhiên, Microsoft khuyến nghị nên sử dụng injection phương thức khởi tạo thay vì sử dụng injection phương thức hoặc injection thủ công bằng RequestServices.

IoC Container tích hợp trong ASP.NET Core

Như đã trình bày ở trên, ASP.NET Core có một IoC container tích hợp để injection sự phụ thuộc tự động. IoC container tích hợp là một container đơn giản nhưng hiệu quả. Ở phần này, chúng ta sẽ tìm hiểu cách hoạt động bên trong của IoC container tích hợp.

Dưới đây là các interface và các lớp quan trọng của IoC container tích hợp:

Interface:

  1. IServiceProvider
  2. IServiceCollection

Lớp:

  1. ServiceProvider
  2. ServiceCollection
  3. ServiceDescription
  4. ServiceCollectionServiceExtensions
  5. ServiceCollectionContainerBuilderExtensions

Sơ đồ sau minh họa mối quan hệ giữa các lớp này:

IoC Container tích hợp trong ASP.NET Core

IServiceCollection

Như bạn đã biết, chúng ta có thể đăng ký các dịch vụ ứng dụng với IoC container tích hợp trong phương thức Configure của lớp Startup bằng cách sử dụng IServiceCollection. Interface IServiceCollection là một interface  rỗng. Nó chỉ kế thừa từ interface IList<servicedescriptor>. Bạn có thể xem mã nguồn ở đây.

Lớp ServiceCollection thực hiện interface IServiceCollection. Xem mã nguồn của lớp ServiceCollection tại đây.

Vì vậy, các dịch vụ bạn thêm các dich vụ vào trong thể hiện của kiểu IServiceCollection, nó tạo ra một thể hiện của lớp ServiceDescriptor và thêm nó vào danh sách.

IServiceProvider

Interface IServiceProvider có phương thức tên GetService. Lớp ServiceProvider thực hiện interface IServiceProvider, nó trả về các dịch vụ đã đăng ký với container. Chúng ta không thể khởi tạo thể hiện lớp ServiceProvider vì các phương thức khởi tạo của nó được đánh dấu bằng chỉ thị truy cập internal.

ServiceCollectionServiceExtensions

Lớp ServiceCollectionServiceExtensions có các phương thức mở rộng liên quan đến đăng ký dịch vụ có thể được sử dụng để thêm các dịch vụ với vòng đời. Các phương thức mở rộng AddSingleton, AddTransient, AddScoped được định nghĩa trong lớp này.

ServiceCollectionContainerBuilderExtensions

Lớp ServiceCollectionContainerBuilderExtensions có phương thức mở rộng BuildServiceProvider được sử dụng để tạo và trả về một thể hiện của ServiceProvider.

Tạo thể hiện của interface IServiceProvider

Có ba cách để tạo một thể hiện của interface IServiceProvider:

Sử dụng IApplicationBuilder

Chúng ta có thể lấy các dịch vụ trong phương thức Configure bằng cách sử dụng thuộc tính ApplicationServices của interface IApplicationBuilder như dưới đây.

public void Configure(IServiceProvider pro, IApplicationBuilder app, IHostingEnvironment env)
{
    var services = app.ApplicationServices;
    var logger = services.GetService<ILog>() }

    //other code removed for clarity 
}

Sử dụng HttpContext

var services = HttpContext.RequestServices;
var log = (ILog)services.GetService(typeof(ILog));

Sử dụng IServiceCollection

public void ConfigureServices(IServiceCollection services)
{
    var serviceProvider = services.BuildServiceProvider();
    var log = (ILog)serviceProvider.GetService(typeof(ILog));
}


Bài viết liên quan:

View Layout, View Start, View Imports là gì? Lợi ích và các sử dụng View Layout, View Start, View Imports trong ASP.NET Core.

Tất tần tật về View, Razor View, Layout View, View Start, View Import, Tag Helpers trong ASP.NET Core MVC.

ActionResult trong ASP.NET Core là gì? Có những loại ActionResult nào? Làm sao để sử dụng ActionResult trong ASP.NET Core.