Dependency Injection là gì ?
Dependency Injection (DI) là một pattern nhằm hỗ trợ lập trình viên chia tách các thành phần khác nhau trong ứng dụng của họ. Nó cung cấp cơ chế cho việc xây dựng các đồ thị phụ thuộc độc lập của các định nghĩa class. Thông qua bài viết này, tôi sẽ tập trung giới thiệu về contructor injection, nơi những phụ thuộc được cung cấp để có thể sử dụng chúng thông qua các contructors. Xem xét những class bên dưới:
class Bar : IBar {
// ...
}
class Foo {
private readonly IBar _bar;
public Foo(IBar bar) {
_bar = bar;
}
}
Trong ví dụ này, Food
phụ thuộc vào IBar
và một số nơi khác chúng ta sẽ phải xây dựng một thể hiện của class Foo
và chỉ định nó phụ thuộc class implementtation Bar
giống như:
var bar = new Bar();
var foo = new Foo(bar);
Có 2 vấn đề ở đây. Đầu tiên, nó vi phạm nguyên tắc thiết kế Dependency Inversion bởi vì class rõ ràng phụ thuộc vào những kiểu cụ thể Bar
và Foo
. Thứ hai, nó dẫn đến một định nghĩa rời rạc của đồ thị phụ thuộc và có thể làm unit testing rất khó khăn.
Pattern Composition Root phát biểu rằng đồ thị phụ thuộc thực thể nên ở một single location "càng gần entry point của ứng dụng càng tốt". Điều này có thể khá lộn xộn nếu không có sự hỗ trợ của một framework. Framework DI cung cấp một cơ chế thường được tham chiếu như một Inversion of Control (IoC) Container, nhằm giảm tải việc khởi tạo, injection và quản lý lifetime của các phụ thuộc cho framework.
Để làm điều này, đơn giản bạn chỉ cần đăng ký các services với một container, tiếp theo bạn có thể load trên top level service. Framework sẽ inject tất cả các service con cho bạn. Môt ví dụ đơn giản, dựa trên định nghĩa class ở trên, có thể trông như thế này:
container.Register<Bar>().As<IBar>();
container.Register<Foo>();
// per the Composition Root pattern, this _should_ be the only lookup on the container
var foo = container.Get<Foo>();
Dependency Injection trong ASP.Net Core
Trước .Net Core, chỉ có cách triển khai DI trong ứng dụng của bạn thông qua sử dụng một framework giống như Autofac, Ninject, StructureMap và nhiều cái khác nữa. Tuy nhiên trong ASP.NET Core, DI tích hợp sẵn trong built-in của framework. Bạn có thể cấu hình container trong phương thức ConfigureService
của class Startup
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IArticleService, ArticleService>();
}
// ...
}
Khi một request được điều hướng đến controller, nó sẽ resolve từ container cùng với tất cả các phụ thuộc.
public class ArticlesController : Controller {
private readonly IArticleService _articleService;
public ArticlesController(IArticleService articleService) {
_articleService = articleService;
}
[HttpGet("{id}"]
public async Task<IActionResult> GetAsync(int id) {
var article = await _articleService.GetAsync(id);
if(article == null)
return NotFound();
return Ok(article);
}
}
Dependency Lifetimes
Tại thời điểm đăng ký, các dependencies yêu cầu định nghĩa một lifetime. Service lifetime định nghĩa điều kiện để một thể hiện new service được tạo. Bên dưới là các lifetimes được định nghĩa bởi framework ASP.NET DI. Thuật ngữ có thể khác biệt nếu bạn chọn sử dụng framework khác.
- Transient - Được tạo mỗi lần chúng được yêu cầu
- Scoped – Được tạo một lần trên mỗi scope. Nói chung, scope thường là tham chiếu đến một web request. Nhưng nó cũng có thể được sử dụng cho bất kỳ công việc nào như thực thi một function trên Azure.
- Singleton – Được tạo chỉ ở lần request đầu tiên. Nếu thể hiện cụ thể được chỉ định tại thời điểm đăng ký, thể hiện này sẽ cung cấp đến tất cả những nơi sử dụng.
Sử dụng các Providers khác
Nếu bạn mong muốn sử dụng một framework DI hoàn thiện hơn, bạn hoàn toàn có thể thực hiện miễn là chúng cung cấp một implentation của IServiceProvider
. Nếu chúng không cung cấp và chỉ là một interface đơn giản thì bạn có thể implement theo cách của mình. Bạn chỉ cần return một thể hiện của container trong phương thức ConfigureServices
. Đây là một ví dụ sử dụng Autofac:
public class Startup {
public IServiceProvider ConfigureServices(IServiceCollection services) {
// setup the Autofac container
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterType<ArticleService>().As<IArticleService>();
var container = builder.Build();
// return the IServiceProvider implementation
return new AutofacServiceProvider(container);
}
// ...
}
Generics
Dependency injection có thể thực sự thú vị khi bạn bắt đầu làm việc với generics. Hầu hết các DI providers cho phép bạn đăng ký những kiểu generic mở. Một ví dụ tuyệt vời ở đây là framework Logging của Microsoft. Bên dưới là cách inject một open generic ILogger<>
:
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
Khi sử dụng trong class bạn có thể truyền type cụ thể cho generic ILogger
:
public class Foo {
public Foo(ILogger<Foo> logger) {
logger.LogInformation("Constructed!!!");
}
}
Một trường hợp sử dụng phổ biến khác là Generic Repository Pattern. Một số xem đây là một anti-parttern khi được sử dụng với một ORM như Entity Framework bởi vì nó đã triển khai Repository pattern. Nhưng nếu bạn chưa quen thuộc với DI và generic, tôi nghĩ nó cung cấp một điểm bắt đầu dễ dàng.
Open generic injection cũng cung cấp một cơ chế tuyệt vời cho các library (như JsonApiDotNetCore
) nhằm đề suất các behaviors mặc định với khả năng mở rộng dễ dàng cho các ứng dụng. Giả sử một framework cung cấp triển khai của pattern generic repository. Nó có thể có một interface giống như sau:
public interface IRepository<T> where T : IIdentifiable {
T Get(int id);
}
Library sẽ cung cấp extension method của IServiceCollection
giống như:
public static void AddDefaultRepositories(this IServiceCollection services) {
services.TryAdd(ServiceDescriptor.Scoped(typeof(IRepository<>), typeof(GenericRepository<>)));
}
Và behavior mặc định có thể được bổ sung bởi ứng dụng trên một tài nguyên cơ bản bởi việc inject một kiểu cụ thể hơn:
services.AddScoped<IRepository<Foo>, FooRepository>();
Và tất nhiên FooRepository
có thể kế thừa từ GenericRepository<>
class FooRepository : GenericRepository<Foo> {
Foo Get(int id) {
var foo = base.Get(id);
// ...authorization of resources or any other application concerns can go here
return foo;
}
}
Bên ngoài web
Team ASP.NET đã chia framework DI của họ từ ASP.NET packages thành Microsoft.Extensions.DependencyInjection
. Điều này có nghĩa là bạn không bị giới hạn với chỉ ứng dụng web và có thể tận dụng những libary mới này trong các ứng dụng hướng sự kiện (như Azure Function và AWS Lambda) hoặc trong các ứng dụng thread loop. Tất cả những gì bạn cần làm là:
- Cài đặt NuGet package:
Install-Package Microsoft.Extensions.DependencyInjection
or
dotnet add package Microsoft.Extensions.DependencyInjection
- Đăng ký các dependencies trên một static container
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<IEmailSender, AuthMessageSender>();
serviceCollection.AddScoped<AzureFunctionEventProcessor, IEventProcessor>();
Container = serviceCollection.BuildServiceProvider();
- Định nghĩa phạm vi lifetime và resolve dependency
var serviceScopeFactory = Container.GetRequiredService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var processor = scope.ServiceProvider.GetService<IEventProcessor>();
processor.Handle(theEvent);
}
Giải phóng Services
Nếu một service được implement interface IDisposable
nó sẽ giải phóng khi conteiner scope được giải phóng. Bạn có thể xem làm thế nào để thực hiện việc này ở đây. Vì lý do này, việc resolve service từ một scope và không phải ở root container là rất quan trọng như miêu tả ở trên. Nếu bạn resolve IDisposable
ở root container bạn có thể tạo ra một rò rỉ bộ nhớ khi đó service sẽ không được giải phóng cho đến khi container giải phóng.
Thảo luận
Dependency injection là một pattern được sử dụng rộng rãi khi phát triển ứng dụng ở hầu hết các ngôn ngữ không chỉ trong .Net. Vì vậy, việc có được hiểu biết về khái niệm này là quan trọng. Trong ASP.NET Core, nó được xây dựng sẵn để chúng ta có thể dễ dàng sử dụng, đồng thời bạn cũng có thể kết hợp các DI libray khác trong ASP.NET Core nếu muốn. Cảm ơn các bạn đã theo dõi bài viết.
Bài viết được dịch từ nguồn:
https://stackify.com/net-core-dependency-injection/