ASP.NET Core: Dependency Injection trong ASP.NET Core


Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên

ASP.NET Core hỗ trợ mẫu thiết kế phần mềm Dependency Injection (DI), đây là một kỹ thuật để đạt được Đảo ngược Kiểm soát (IoC) giữa các lớp và các phần phụ thuộc của chúng.

Để biết thêm thông tin cụ thể về DI trong controller MVC, hãy xem Nội dung phụ thuộc vào controller trong ASP.NET Core.

Để biết thông tin về cách sử dụng DI trong các ứng dụng không phải ứng dụng web, hãy xem Dependency Injection trong .NET.

Để biết thêm thông tin về việc đưa các tùy chọn vào phần phụ thuộc, hãy xem Mẫu tùy chọn trong ASP.NET Core.

Chủ đề này cung cấp thông tin về DI trong ASP.NET Core. Tài liệu chính về việc sử dụng DI có trong Dependency Injection trong .NET.

Xem hoặc tải xuống mã mẫu (cách tải xuống)

Tổng quan về Dependency Injection

Một dependency là một đối tượng mà một đối tượng khác phụ thuộc vào. Kiểm tra lớp MyDependency sau với một phương thức WriteMessage mà các lớp khác phụ thuộc vào:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Một lớp có thể tạo một thể hiện của lớp MyDependency để sử dụng phương thức WriteMessage của nó. Trong ví dụ sau, lớp MyDependency là một phụ thuộc của lớp IndexModel:

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

Lớp tạo và phụ thuộc trực tiếp vào lớp MyDependency. Các phụ thuộc code, chẳng hạn như trong ví dụ trên, có vấn đề và nên tránh vì những lý do sau:

  • Để thay thế MyDependency bằng một triển khai khác, thì lớp IndexModel phải được sửa đổi.
  • Nếu MyDependency có các phụ thuộc, chúng cũng phải được cấu hình bởi lớp IndexModel. Trong một dự án lớn có nhiều lớp tùy thuộc vào MyDependency, code cấu hình sẽ nằm rải rác trên ứng dụng.
  • Việc triển khai này rất khó để kiểm tra đơn vị.

Dependency Injection giải quyết những vấn đề này thông qua:

  • Việc sử dụng một interface hoặc lớp cơ sở để trừu tượng hóa việc triển khai phụ thuộc.
  • Đăng ký phần phụ thuộc trong vùng chứa dịch vụ. ASP.NET Core cung cấp một bộ chứa dịch vụ tích hợp sẵn, đó là IServiceProvider. Các dịch vụ thường được đăng ký trong file Program.cs của ứng dụng.
  • Injection dịch vụ vào hàm tạo của lớp mà nó được sử dụng. Framework đảm nhận trách nhiệm tạo một thể hiện của sự phụ thuộc và xử lý nó khi không còn cần thiết.

Trong ứng dụng mẫu, interface IMyDependency khai báo phương thức WriteMessage:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Interface này được thực hiện bởi một kiểu cụ thể, đó là MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Ứng dụng mẫu đăng ký dịch vụ IMyDependency với kiểu cụ thể MyDependency. Phương thức AddScoped đăng ký dịch vụ với thời gian tồn tại trong phạm vi, thời gian tồn tại của một yêu cầu. Tuổi thọ của dịch vụ được mô tả sau trong chủ đề này.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

Trong ứng dụng mẫu, dịch vụ IMyDependency được yêu cầu và được sử dụng để gọi phương thức WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Bằng cách sử dụng mẫu DI, controller hoặc Razor Page:

  • Không sử dụng kiểu cụ thể MyDependency, chỉ thực thi interface IMyDependency. Điều đó giúp dễ dàng thay đổi việc triển khai mà không cần sửa đổi controller hoặc Razor Page.
  • Không tạo thể hiện của MyDependency, nó được tạo bởi bộ chứa DI.

Việc thực thi interface IMyDependency có thể được cải thiện bằng cách sử dụng API ghi nhật ký tích hợp:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Bản cập nhật file Program.cs sẽ tiến hành đăng ký thực thi IMyDependency mới:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 phụ thuộc vào ILogger<TCategoryName> mà nó yêu cầu trong hàm tạo. ILogger<TCategoryName> là một dịch vụ do framework cung cấp.

Không có gì lạ khi sử dụng DI theo kiểu chuỗi. Mỗi phụ thuộc được yêu cầu lần lượt yêu cầu các phụ thuộc của chính nó. Vùng chứa giải quyết các phụ thuộc trong biểu đồ và trả về dịch vụ được giải quyết đầy đủ. Tập hợp chung các phụ thuộc phải được giải quyết thường được gọi là cây phụ thuộcđồ thị phụ thuộc hoặc đồ thị đối tượng.

Vùng chứa giải quyết ILogger<TCategoryName> bằng cách tận dụng các kiểu mở (generic), loại bỏ nhu cầu đăng ký mọi kiểu được xây dựng (generic).

Trong thuật ngữ DI, một dịch vụ:

  • Thường là một đối tượng cung cấp dịch vụ cho các đối tượng khác, chẳng hạn như dịch vụ IMyDependency.
  • Không liên quan đến dịch vụ web, mặc dù dịch vụ này có thể sử dụng dịch vụ web.

Framework này cung cấp một hệ thống ghi nhật ký mạnh mẽ. Việc thực thi IMyDependency hiển thị trong các ví dụ trên được viết để minh họa DI cơ bản, không phải để triển khai ghi nhật ký. Hầu hết các ứng dụng không cần phải ghi nhật ký. Đoạn mã sau minh họa việc sử dụng ghi nhật ký mặc định, không yêu cầu đăng ký bất kỳ dịch vụ nào:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Sử dụng đoạn code trên sẽ không cần phải cập nhật Program.cs, vì việc ghi nhật ký được cung cấp bởi framework.

Đăng ký các nhóm dịch vụ với các phương thức mở rộng

Framework ASP.NET Core sử dụng quy ước để đăng ký một nhóm các dịch vụ liên quan. Quy ước là sử dụng một phương thức mở rộng Add{GROUP_NAME} duy nhất để đăng ký tất cả các dịch vụ theo yêu cầu của tính năng framework. Ví dụ: phương thức mở rộng AddControllers đăng ký các dịch vụ cần thiết cho controller MVC.

Đoạn code sau đây được tạo bởi mẫu Razor Pages bằng cách sử dụng tài khoản người dùng cá nhân và cho biết cách thêm các dịch vụ bổ sung vào vùng chứa bằng các phương thức tiện ích mở rộng  AddDbContext và AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Hãy xem xét những điều sau đây đăng ký dịch vụ và cấu hình các tùy chọn:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Các nhóm đăng ký liên quan có thể được chuyển sang phương thức mở rộng để đăng ký dịch vụ. Ví dụ: các dịch vụ cấu hình được thêm vào lớp sau:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Các dịch vụ còn lại được đăng ký trong một lớp tương tự. Đoạn mã sau sử dụng các phương thức tiện ích mở rộng mới để đăng ký dịch vụ:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Lưu ý: Mỗi phương thức mở rộng services.Add{GROUP_NAME} thêm và có khả năng định cấu hình dịch vụ. Ví dụ: AddControllersWithViews thêm các dịch vụ mà controller MVC có view yêu cầu và AddRazorPages thêm các dịch vụ mà Razor Pages yêu cầu.

Tuổi thọ của dịch vụ

Xem Thời gian tồn tại của dịch vụ trong Dependency Injection trong .NET

Để sử dụng các dịch vụ có phạm vi trong middleware, hãy sử dụng một trong các phương pháp sau:

  • Đưa dịch vụ vào phương thức Invoke hoặc của middleware InvokeAsync. Việc sử dụng injection hàm tạo sẽ đưa ra một ngoại lệ thời gian chạy vì nó buộc dịch vụ được xác định phạm vi hoạt động giống như một đơn vị. Mẫu trong phần Tùy chọn đăng ký và trọn đời thể hiện cách tiếp cận InvokeAsync.
  • Sử dụng middleware dựa trên Factory. Middleware đã đăng ký sử dụng phương pháp này được kích hoạt theo yêu cầu của khách hàng (kết nối), cho phép các dịch vụ trong phạm vi được đưa vào hàm tạo của middleware.

Để biết thêm thông tin, hãy xem Viết middleware ASP.NET Core tùy chỉnh.

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

Xem Các phương thức đăng ký dịch vụ trong Dependency Injection trong .NET

Việc sử dụng nhiều triển khai khi mô phỏng các kiểu để thử nghiệm là điều phổ biến.

Đăng ký một dịch vụ chỉ với một loại triển khai tương đương với việc đăng ký dịch vụ đó với cùng một loại triển khai và dịch vụ. Đây là lý do tại sao không thể đăng ký nhiều lần triển khai dịch vụ bằng các phương thức không sử dụng loại dịch vụ rõ ràng. Các phương thức này có thể đăng ký nhiều thể hiện của một dịch vụ, nhưng tất cả chúng sẽ có cùng kiểu thực thi.

Bất kỳ phương thức đăng ký dịch vụ nào ở trên đều có thể được sử dụng để đăng ký nhiều thể hiện dịch vụ của cùng một loại dịch vụ. Trong ví dụ sau, AddSingleton được gọi hai lần với IMyDependency như là kiểu dịch vụ. Lời gọi thứ hai để AddSingleton ghi đè lời gọi trước đó khi được giải quyết IMyDependency và thêm vào lời gọi trước đó khi nhiều dịch vụ được giải quyết qua IEnumerable<IMyDependency>. Các dịch vụ xuất hiện theo thứ tự chúng đã được đăng ký khi được giải quyết qua IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Hành vi injection hàm tạo

Xem Hành vi injection hàm tạo trong Dependency injection trong .NET

Ngữ cảnh Entity Framework

Theo mặc định, ngữ cảnh Entity Framework được thêm vào bộ chứa dịch vụ bằng cách sử dụng thời gian tồn tại trong phạm vi vì các hoạt động cơ sở dữ liệu ứng dụng web thường được đặt trong phạm vi yêu cầu của máy khách. Để sử dụng thời gian tồn tại khác, hãy chỉ định thời gian tồn tại bằng cách sử dụng quá tải AddDbContext. Các dịch vụ có thời gian tồn tại nhất định không được sử dụng database context có thời gian tồn tại ngắn hơn thời gian tồn tại của dịch vụ.

Tùy chọn trọn đời và đăng ký

Để chứng minh sự khác biệt giữa thời gian tồn tại của dịch vụ và các tùy chọn đăng ký của chúng, hãy xem xét các giao diện sau biểu thị một tác vụ dưới dạng hoạt động với mã định danh là OperationId. Tùy thuộc vào thời gian tồn tại của dịch vụ của một hoạt động được định cấu hình cho các interface sau, bộ chứa cung cấp các phiên bản giống hoặc khác nhau của dịch vụ khi được một lớp yêu cầu:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Lớp Operation sau thực thi tất cả các interface ở trên. Hàm tạo Operation tạo GUID và lưu trữ 4 ký tự cuối cùng trong property OperationId:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Đoạn mã sau tạo nhiều đăng ký của lớp Operation theo thời gian tồn tại được đặt tên:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Ứng dụng mẫu thể hiện thời gian tồn tại của đối tượng cả trong và giữa các yêu cầu. IndexModel và middleware yêu cầu từng loại IOperation và ghi nhật ký OperationId cho từng loại:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Tương tự như IndexModel, middleware giải quyết các dịch vụ tương tự:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Các dịch vụ có phạm vi và nhất thời phải được giải quyết theo phương thức InvokeAsync:

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Đầu ra của trình ghi cho thấy:

  • Các đối tượng thoáng qua luôn khác nhau. Giá trị nhất thời OperationId là khác nhau trong IndexModel và trong middleware.
  • Các đối tượng trong phạm vi giống nhau đối với một yêu cầu nhất định nhưng khác nhau đối với mỗi yêu cầu mới.
  • Các đối tượng Singleton giống nhau cho mọi yêu cầu.

Để giảm đầu ra ghi nhật ký, hãy đặt "Logging:LogLevel:Microsoft:Error" trong file appsettings.Development.json :

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Giải quyết một dịch vụ khi khởi động ứng dụng

Đoạn mã sau cho biết cách giải quyết một dịch vụ có phạm vi trong khoảng thời gian giới hạn khi ứng dụng khởi động:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Xác thực phạm vi

Xem Hành vi injection hàm tạo trong Dependency Injection trong .NET

Để biết thêm thông tin, hãy xem Xác thực phạm vi.

Yêu cầu dịch vụ

Các dịch vụ và phần phụ thuộc của chúng trong một yêu cầu ASP.NET Core được hiển thị thông qua HttpContext.RequestServices.

Framework tạo phạm vi cho mỗi yêu cầu và RequestServices hiển thị nhà cung cấp dịch vụ có phạm vi. Tất cả các dịch vụ trong phạm vi đều hợp lệ miễn là yêu cầu đang hoạt động.

Ghi chú

Nên yêu cầu các phụ thuộc làm tham số hàm tạo hơn là giải quyết các dịch vụ từ RequestServices. Yêu cầu các phụ thuộc làm tham số hàm tạo sẽ tạo ra các lớp dễ kiểm tra hơn.

Dịch vụ thiết kế cho DI

Khi thiết kế các dịch vụ để tiêm phụ thuộc:

  • Tránh các lớp và thành viên có trạng thái và tĩnh. Thay vào đó, hãy tránh tạo trạng thái global bằng cách thiết kế các ứng dụng để sử dụng các dịch vụ đơn lẻ.
  • Tránh khởi tạo trực tiếp các lớp phụ thuộc trong các dịch vụ. Khởi tạo trực tiếp kết hợp code với một triển khai cụ thể.
  • Làm cho các dịch vụ trở nên nhỏ gọn, được tính toán hợp lý và dễ dàng kiểm tra.

Nếu một lớp có nhiều phụ thuộc được đưa vào, đó có thể là dấu hiệu cho thấy lớp đó có quá nhiều trách nhiệm và vi phạm Nguyên tắc Trách nhiệm Đơn lẻ (SRP). Cố gắng cấu trúc lại lớp bằng cách chuyển một số trách nhiệm của nó sang các lớp mới. Hãy nhớ rằng các lớp mô hình trang Razor Pages và các lớp controller MVC nên tập trung vào các mối quan tâm về giao diện người dùng.

Xử lý các dịch vụ

Vùng chứa gọi Dispose cho các kiểu IDisposable mà nó tạo. Các dịch vụ được giải quyết từ vùng chứa không bao giờ được nhà phát triển loại bỏ. Nếu một kiểu hoặc factory được đăng ký dưới dạng một singleton, thì vùng chứa sẽ tự động loại bỏ singleton đó.

Trong ví dụ sau, các dịch vụ được tạo bởi bộ chứa dịch vụ và được loại bỏ tự động: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

Console gỡ lỗi hiển thị đầu ra sau mỗi lần làm mới trang Index:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Các dịch vụ không được tạo bởi bộ chứa dịch vụ

Hãy xem xét đoạn mã sau:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Trong đoạn code trên:

  • Các phiên bản dịch vụ không được tạo bởi bộ chứa dịch vụ.
  • Framework không tự động xử lý các dịch vụ.
  • Nhà phát triển chịu trách nhiệm xử lý các dịch vụ.

Hướng dẫn IDisposable dành cho Phiên bản tạm thời và phiên bản dùng chung

Xem hướng dẫn IDisposable cho Phiên bản tạm thời và được chia sẻ trong Dependency Injection trong .NET

Thay thế vùng chứa dịch vụ mặc định

Xem Thay thế bộ chứa dịch vụ mặc định trong Dependency Injection trong .NET

Khuyến nghị

Xem các đề xuất trong Dependency Injection trong .NET

  • Tránh sử dụng mẫu định vị dịch vụ. Ví dụ: không gọi GetService để lấy phiên bản dịch vụ khi bạn có thể sử dụng DI thay thế:

Như sau sẽ không đúng:

Code không chính xác

Như sau là chính xác:

public class MyClass
{
    private readonly IOptionsMonitor<MyOptions> _optionsMonitor;

    public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
    {
        _optionsMonitor = optionsMonitor;
    }

    public void MyMethod()
    {
        var option = _optionsMonitor.CurrentValue.Option;

        ...
    }
}
  • Một biến thể định vị dịch vụ khác cần tránh là đưa vào một factory giải quyết các phụ thuộc trong thời gian chạy. Cả hai thực tế này kết hợp các chiến lược Đảo ngược Kiểm soát.
  • Tránh truy cập tĩnh vào HttpContext (ví dụ: IHttpContextAccessor.HttpContext).

DI là một giải pháp thay thế cho các mẫu truy cập đối tượng tĩnh/toàn cục. Bạn có thể không nhận ra được những lợi ích của DI nếu bạn kết hợp nó với truy cập đối tượng tĩnh.

Các mẫu được đề xuất cho nhiều bên thuê trong DI

Orchard Core là một framework ứng dụng để xây dựng các ứng dụng mô-đun nhiều bên thuê trên ASP.NET Core. Để biết thêm thông tin, hãy xem Tài liệu cốt lõi của Orchard.

Xem các mẫu Orchard Core để biết ví dụ về cách xây dựng các ứng dụng mô-đun và nhiều bên thuê chỉ sử dụng Orchard Core Framework mà không có bất kỳ tính năng cụ thể nào của CMS.

Các dịch vụ do framework cung cấp

Program.cs đăng ký các dịch vụ mà ứng dụng sử dụng, bao gồm các tính năng nền tảng, chẳng hạn như Entity Framework Core và ASP.NET Core MVC. Ban đầu, dịch vụ IServiceCollection được cung cấp để Program.cs có các dịch vụ được xác định bởi framework tùy thuộc vào cách máy chủ được định cấu hình. Đối với các ứng dụng dựa trên mẫu ASP.NET Core, framework đăng ký hơn 250 dịch vụ.

Bảng sau liệt kê một mẫu nhỏ các dịch vụ đã đăng ký theo khung này:

Loại dịch vụ Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient
IHostApplicationLifetime Singleton
IWebHostMôi trường Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transient
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Tài nguyên bổ sung

Nguồn: learn.microsoft.com
» Tiếp: Triển khai ứng dụng web ASP.NET
Khóa học qua video:
Lập trình Python All Lập trình C# All SQL Server All Lập trình C All Java PHP HTML5-CSS3-JavaScript
Đăng ký Hội viên
Tất cả các video dành cho hội viên
Copied !!!