Lưu ý: đây là bản cập nhật của một bài viết cũ không còn khả dụng từ blog cũ của tôi.
Mục lục
Giới thiệu
Phân Giải Dependency (RS), Tiêm Dependency/Đảo Ngược Dependency (DI) và Đảo Ngược Điều Khiển (IoC) từ lâu đã là những chủ đề nóng trong phát triển phần mềm. Hầu hết các framework đều nhận thức về nó hoặc cung cấp một số cơ chế để giúp triển khai. Tất cả bắt đầu từ rất lâu, tuy nhiên, mọi thứ hiện tại hơi rối rắm – nhưng sẽ trở nên tốt hơn!
Trong bài viết này, tôi sẽ không đi qua tất cả chi tiết của tất cả các thư viện phân giải dependency đang tồn tại, thay vào đó tôi chỉ tập trung vào các thư viện của Microsoft. Đồng thời, tôi sẽ chỉ nói về phân giải dependency tổng quát, bỏ qua các cách sử dụng chuyên biệt hơn, như WCF, WF và SharePoint, vốn cũng sử dụng các khái niệm tương tự.
Nguồn gốc
Tất cả bắt đầu với giao diện lâu đời IServiceProvider. Nó cơ bản cung cấp một phương thức duy nhất, GetService, để trả lời câu hỏi “cho tôi một implementation cho kiểu này”. Chỉ vậy thôi, tham số duy nhất là một Type, và kết quả trả về là một đối tượng duy nhất. Không có implementation công khai nào của nó, nhưng các designer Windows Forms – code tự động sinh ra – sử dụng nó rất nhiều để yêu cầu dịch vụ từ các bề mặt thiết kế (tức là, Visual Studio designer). Bạn có thể tự do triển khai nó theo bất kỳ cách nào, khuyến nghị duy nhất là trả về null trong trường hợp không tìm thấy dịch vụ, thay vì ném ra ngoại lệ.
ASP.NET MVC
ASP.NET MVC bắt đầu như một tùy chọn template tích hợp trong Visual Studio nhưng sau đó chuyển sang triển khai qua NuGet, đây là cách Microsoft hiện cung cấp các thư viện phát triển nhanh của mình. MVC 4 giới thiệu container tiêm dependency và API riêng của nó. Nó được xây dựng xung quanh giao diện IDependencyResolver, không triển khai IServiceProvider, mặc dù nó cung cấp phương thức GetService có chữ ký chính xác giống nhau – và các yêu cầu giống nhau – như của IServiceProvider. Nó cũng thêm một phương thức khác, GetServices, để truy xuất tất cả các implementation của một dịch vụ nhất định, một lần nữa được xác định bởi Type của nó.
Lần này chúng ta có một điểm đăng ký, đó là DependencyResolver.Current. Bạn có thể đặt nó thành implementation tùy chỉnh của mình, hoặc sử dụng mặc định, thực chất không trả về bất cứ thứ gì.
ASP.NET Web API
Web API ra đời muộn hơn MVC một chút, và, trong khi chia sẻ triết lý của nó, và cũng được cung cấp thông qua NuGet, đã cung cấp API riêng của nó, cụ thể là cho tiêm dependency. Cũng có giao diện IDependencyResolver, một lần nữa, không kế thừa từ IServiceProvider, nhưng với kế thừa phức tạp hơn một chút: giờ đây chúng ta cũng có IDependencyScope, nơi khai báo các phương thức GetService và GetServices, cũng như IDisposable, có lẽ để chúng ta có thể có các phạm vi phân giải dependency. Điểm đăng ký nổi tiếng là GlobalConfiguration.Configuration.DependencyResolver.
ASP.NET SignalR
SignalR là một thứ gì đó khác biệt trong stack ASP.NET. Giống như MVC và Web API, nó được cung cấp (và vẫn còn, phải nói vậy) như một gói NuGet riêng biệt. Không có gì ngạc nhiên khi nó cũng cung cấp API phân giải dependency riêng của mình, dưới dạng IDependencyResolver. Một lần nữa, không liên quan đến IServiceProvider, và như vậy cung cấp một vài phương thức khác: bên cạnh GetService cổ điển (cùng chữ ký và hợp đồng), chúng ta cũng có GetServices, và thậm chí một phương thức đăng ký (RegisterType với một vài overload). Nó cũng là IDisposable, có lẽ để kiểm soát các phạm vi đăng ký. Điểm đăng ký có sẵn dưới dạng GlobalHost.DependencyResolver và có một implementation mặc định, được đặt tên phù hợp là DefaultDependencyResolver.
Entity Framework 6
Rời khỏi vùng đất ASP.NET một chút, Entity Framework 6 cũng giới thiệu API riêng (tất nhiên…) của nó cho phân giải dependency. IDbDependencyResolver cũng cung cấp các phương thức cổ điển GetService và GetServices, nhưng lần này, những phương thức này cũng nhận một tham số key tùy chọn. GetService không nên ném ngoại lệ nếu không tìm thấy dịch vụ phù hợp, và GetServices nên trả về một enumeration rỗng tương tự. Việc đăng ký được thực hiện thông qua DbConfiguration.DependencyResolver, không có implementation mặc định công khai. EF 6 mong đợi một số dịch vụ được cung cấp thông qua phân giải dependency, nếu không, nó sẽ sử dụng các mặc định tích hợp sẵn của chính nó.
Entity Framework Core 7
EF Core 7 xáo trộn mọi thứ một chút. Trước hết, constructor DbContext giờ đây có thể nhận một instance của IServiceProvider hoặc bất kỳ dịch vụ đã đăng ký nào khác, miễn là nó được lấy từ DI.
Unity
Unity là một phần của Enterprise Library của Microsoft và những độc giả lâu năm của blog tôi nên biết rằng đó là thứ tôi từng sử dụng cho đảo ngược điều khiển (IoC), tiêm dependency (DI) và lập trình hướng khía cạnh (AOP). Là một container IoC, nó bao gồm API riêng của nó, IUnityContainer, cũng cung cấp các phương thức phân giải dịch vụ, bên cạnh rất nhiều thứ khác; lần này, tên là Resolve và ResolveAll, với nhiều overload và phiên bản generic cũng như non-generic. Resolve có thể nhận một key tùy chọn, nhưng một điểm khác biệt lớn là implementation mặc định (UnityContainer) sẽ ném ngoại lệ nếu không tìm thấy dịch vụ.
Common Service Locator
Vì có rất nhiều thư viện phân giải dependency ngoài kia, cung cấp các dịch vụ khái niệm tương tự nhưng với API khác nhau, Microsoft đã tài trợ một thư viện mã nguồn mở để định nghĩa một giao diện chung cho phân giải dependency mà các bên quan tâm có thể tuân thủ, hoặc tốt hơn, viết adapter cho. Mã có sẵn trên Codeplex (đã biến mất lâu rồi) và NuGet và một số tác giả thư viện từng cung cấp adapter cho Common Service Locator, như Unity, Castle Windsor, Spring.NET, StructureMap, Autofac, MEF, LinFu, Ninject, v.v. Xem danh sách các gói NuGet khớp với Common Service Locator tại đây. API Common Service Locator chỉ quy định hai họ phương thức trong API IServiceLocator của nó: GetInstance và GetAllInstances. Thú vị là, IServiceLocator kế thừa từ IServiceProvider, và nó cũng có một key tùy chọn cho GetInstance, giống như EF6 và Unity, vì đây là trường hợp tổng quát hơn – nhiều đăng ký cho cùng một kiểu dưới các key khác nhau, key mặc định là null.
.NET Core
Microsoft đã đi đúng hướng. MVC, Web API và SignalR được hợp nhất dưới ASP.NET Core, vì vậy các cơ chế phân giải dependency phải giống nhau.
Giờ đây chúng ta có ba vòng đời khác nhau, và cơ chế không thực sự (dễ dàng) mở rộng:
- Singleton: một thực thể cho toàn bộ vòng đời của container DI
- Transient: một thực thể sẽ được tạo ra mỗi lần được yêu cầu
- Scoped: một thực thể cho mỗi phạm vi (trong các ứng dụng ASP.NET Core, một phạm vi là một request)
Giao diện IServiceCollection cho phép đăng ký dịch vụ (không có key, nhưng cho phép nhiều đăng ký cho cùng một kiểu). IServiceProvider là thứ được sử dụng để truy xuất một dịch vụ.
.NET
.NET Core cũng đã biến mất và chúng ta có .NET thay thế. Tính đến .NET 8, DI giờ đây hỗ trợ cả đăng ký có tên cũng như không tên, được xử lý như thể key là một chuỗi rỗng. Tất cả các công nghệ liên quan đến .NET hiện sử dụng cùng các kiểu và khái niệm.
Có các extension cho IServiceProvider cho phép chúng ta truy xuất:
- Một dịch vụ tùy chọn không tên/có tên duy nhất theo Type hoặc theo một ràng buộc generic
- Một dịch vụ bắt buộc không tên/có tên duy nhất
- Tất cả các implementation của một dịch vụ nhất định
Common Service Locator giờ đã biến mất, vì tất cả các framework DI cho .NET hiện sử dụng IServiceProvider, và tôi nghĩ đó là một điều tốt.
Kết luận
Tôi muốn khôi phục bài viết cũ này, vốn đã biến mất khỏi blog cũ của tôi, và cập nhật nó với tình trạng hiện tại. Hy vọng bạn thấy nó thú vị! 😉



