Dependency injection (DI) trong C# với ServiceCollection

Chúng tôi rất vui mừng được chia sẻ kiến thức sâu sắc về từ khóa Dependency injection c la gi để tối ưu hóa nội dung trang web và chiến dịch tiếp thị trực tuyến. Bài viết cung cấp phương pháp tìm kiếm, phân tích và lựa chọn từ khóa phù hợp, cùng với chiến lược và công cụ hữu ích. Hy vọng thông tin này sẽ giúp bạn xây dựng chiến lược thành công và thu hút lưu lượng người dùng. Cảm ơn sự quan tâm và hãy tiếp tục theo dõi blog để cập nhật kiến thức mới nhất.

  • Inversion of Control
  • Dependency injection
  • Không ứng dụng kỹ thuật DI
  • Ứng dụng kỹ thuật DI
  • Những kiểu DI
  • DI Container
  • Lớp ServiceCollection
  • Lớp ServiceProvider
  • Sử dụng DI cơ bản
  • Sử dụng Delegate / Factor để đăng ký dịch vụ
  • Sử dụng Options khởi tạo dịch vụ
  • Sử dụng cấu hình từ file thiết lập

Inversion of Control (IoC) / Dependency inversion

Inversion of Control (IoC – Đảo ngược tinh chỉnh) là một nguyên tắc thiết kế trong công nghệ phần mềm trong đó các thành phần nó dựa vào để thao tác bị đảo ngược quyền tinh chỉnh khi so sánh với lập trình hướng thủ thục truyền thống.

Bạn Đang Xem: Dependency injection (DI) trong C# với ServiceCollection

Khi ứng dụng cho những đối tượng người sử dụng lớp (dịch vụ) có thể gọi nó là Dependency inversion (đảo ngược phụ thuộc), để diễn giải trước tiên cần nắm rõ khái niệm Dependency (phụ thuộc)

dependency : Giả sử bạn có một lớp classA, lớp này còn có sử dụng một chức năng từ đối tượng người sử dụng lớp classB (classA hoạt động dựa vào classB). Lúc đó classB gọi là phụ thuộc (dependency) (của classA)

Thiết kế truyền thống – tham chiếu trực tiếp đến Dependency

Có lớp class A có sử dụng một chức năng (gọi hàm ào đó) của class B, lớp class B lại tham chiếu và gọi các chức năng có trong class C. Ta thấy class A dựa vào class B để hoạt động, class B dựa vào class C. Nếu vậy khi thiết kế Theo phong cách thông thường, viết code thì class A có tham chiếu trực tiếp (cứng) đến class B và trong class B có tham chiếu đến class C (thể hiện như hình dưới).

di tham chiếu dependency trực tiếp

Sự phụ thuộc đối tượng người sử dụng này vào đối tượng người sử dụng khác ở thời khắc viết code và thời khắc thực thi là hoàn toàn giống nhau.

class ClassC { public void ActionC() => Console.WriteLine(“kích hoạt in ClassC”); } class ClassB { // Phụ thuộc của ClassB là ClassC ClassC c_dependency; public ClassB(ClassC classc) => c_dependency = classc; public void ActionB() { Console.WriteLine(“kích hoạt in ClassB”); c_dependency.ActionC(); } } class ClassA { // Phụ thuộc của ClassA là ClassB ClassB b_dependency; public ClassA(ClassB classb) => b_dependency = classb; public void ActionA() { Console.WriteLine(“kích hoạt in ClassA”); b_dependency.ActionB(); } }

Khi sử dụng:

ClassC objectC = new ClassC(); ClassB objectB = new ClassB(objectC); ClassA objectA = new ClassA(objectB); objectA.ActionA(); // Kết quả: // kích hoạt in ClassA // kích hoạt in ClassB // kích hoạt in ClassC

Thiết kế Theo phong cách đảo ngược phụ thuộc Inverse Dependency

Cách viết code này, ở thời khắc thực thi thì class A vẫn gọi được hàm có class B, class B vẫn gọi hàm có class C tức thị kết quả không đổi. Tuy nhiên, khi thiết kế ở thời khắc viết code (trong code) class A không tham chiếu trực tiếp đến class B mà nó lại sử dụng interface (hoặc lớp abstruct) mà classB triển khai. Điều này dẫn tới sự phụ thuộc lỏng lẻo giữa classAclassB (xem hình)

di tham chiếu dependency một cách lỏng lẻo

Khi thực thi, classB có thể được thay thế bởi bất kỳ lớp nào triển khai từ giao điện interface B, classB cụ thể mà classA sử dụng được quyết định và điểu khiển bởi interface B (điều này còn có nghĩa vì sao gọi là đảo ngược phụ thuộc)

interface IClassB { public void ActionB(); } interface IClassC { public void ActionC(); } class ClassC : IClassC { public ClassC() => Console.WriteLine (“ClassC is created”); public void ActionC() => Console.WriteLine(“kích hoạt in ClassC”); } class ClassB : IClassB { IClassC c_dependency; public ClassB(IClassC classc) { c_dependency = classc; Console.WriteLine(“ClassB is created”); } public void ActionB() { Console.WriteLine(“kích hoạt in ClassB”); c_dependency.ActionC(); } } class ClassA { IClassB b_dependency; public ClassA(IClassB classb) { b_dependency = classb; Console.WriteLine(“ClassA is created”); } public void ActionA() { Console.WriteLine(“kích hoạt in ClassA”); b_dependency.ActionB(); } }

Kết quả sử dụng khi chạy là tương tự

IClassC objectC = new ClassC(); IClassB objectB = new ClassB(objectC); ClassA objectA = new ClassA(objectB); objectA.ActionA();

Code dễ dàng thay các phụ thuộc, ví dụ, khái niệm thêm:

class ClassC1 : IClassC { public ClassC1() => Console.WriteLine (“ClassC1 is created”); public void ActionC() { Console.WriteLine(“kích hoạt in C1”); } } class ClassB1 : IClassB { IClassC c_dependency; public ClassB1(IClassC classc) { c_dependency = classc; Console.WriteLine(“ClassB1 is created”); } public void ActionB() { Console.WriteLine(“kích hoạt in B1”); c_dependency.ActionC(); } }

Khi sử dụng, có thể thay thế các dependency tùy thuộc vào mục tiêu sử dụng:

IClassC objectC = new ClassC1(); // new ClassC(); IClassB objectB = new ClassB1(objectC); // new ClassB(); ClassA objectA = new ClassA(objectB); objectA.ActionA();

Các kiểu Dependency Injection

Từ phương pháp một dependency được đưa vào đối tường cần nó thì được phân chia có ba kiểu DI:

  • Inject thông qua phương thức khởi tạo: cung cấp các Dependency cho đối tượng người sử dụng thông qua hàm khởi tạo ( như đã thực hiện ở ví dụ trên) – tập trung vào cách này vì thư viện .NET tương trợ sẵn
  • Inject thông qua setter: tức các Dependency như thể tính chất của lớp, sau đó inject bằng gán tính chất cho Depedency object.denpendency = obj;
  • Inject thông qua các Interface – xây dựng Interface có chứa các phương thức Setter để thiết lập dependency, interface này sử dụng bởi các lớp triển khai, lớp triển khai phải khái niệm các setter quy định trong interface

Trong ba kiểu Inject thì Inject qua phương thức khởi tạo rất phổ quát vì tính linh hoạt, mềm mỏng, dễ xây dựng thư viện DI…

Ví dụ, xây dựng lại code phần trên với kỹ thuật INJECT BẰNG HÀM TẠO kết phù hợp với thiết kế phụ thuộc lỏng lẻo giữa các dependency (Dependency Inverse) ở trên.

Trước hết xây dựng một interface là IHorn

public interface IHorn { void Beep (); }

Lớp Car được xây dựng để sử dụng IHorn như thể Dependency

public class Car { IHorn horn; // IHorn (Interface) là một Dependecy của Car public Car (IHorn horn) => this.horn = horn; // Inject từ hàm tạo public void Beep () => horn.Beep (); }

Xem Thêm : Luật tâm thức là gì? Không gian của trí tuệ, tâm trí và tiềm thức – Review Hay Hay

Với cách triển khai DI bằng phương thức khởi tạo như vậy, kết phù hợp với sự phụ thuộc lỏng giữa Car và các lớp triển khai IHorn. Thì sử dụng Car tạo ra các đối tượng người sử dụng cụ thể rất linh hoạt và độc lập với nhiều loại đối tượng người sử dụng triển khai IHorn, ví dụ thử tạo ra hai loại còi một chiếc loại lơn và một chiếc loại nhẹ

public class HeavyHorn : IHorn { public void Beep() => Console.WriteLine(“(kêu to lắm) BEEP BEEP BEEP …”); } public class LightHorn : IHorn { public void Beep() => Console.WriteLine(“(kêu bé lắm) beep bep bep …”); }

Lúc này khi sử dụng, Car của bạn có dùng loại còi nào thì dùng – logic code giống nhau

Car car1 = new Car(new HeavyHorn()); car1.Beep(); // (kểu to lắm) BEEP BEEP BEEP … Car car2 = new Car(new LightHorn()); car2.Beep(); // (kểu bé lắm) beep bep bep …

Inject bằng phương thức khởi tạo nên tập trung vào đó, vì các thư viện DI tương trợ tốt

Toàn bộ phần trên là lý thuyết cơ bản, triển khai thực tế thì nên cần có một dịch vụ trung tâm gọi là DI Container, tại đó các lớp (dịch vụ) đăng ký vào, sau đó khi sư dụng dịch vụ nào nó tự động hóa tạo ra dịch vụ đó, nếu dịch vụ đó cần dependency nào nó cũng tự tạo dependency và tự động hóa bơm vào dịch vụ cho chung ta. Để tự xây dựng ra một DI Container rất phức tạp, nên ở đây ta không nỗ lực xây dựng một DI Container riêng, thay vào đó ta sẽ sử dụng các thư viện tương trợ sẵn cho .NET

DI Container

Mục tiêu sử dụng DI, để tạo ra các đối tượng người sử dụng dịch vụ kéo theo là các Dependency của đối tượng người sử dụng đó. Để làm điều này ta cần sử dụng đến những thư viện, có rất nhiều thư viện DI – Container (cơ chứa chứa và quản lý các dependency) như: Windsor, Unity Ninject, DependencyInjection …

Trong số đó DependencyInjection là DI Container mặc định của ASP.NET Core, phần này tìm hiểu về DI Container này Microsoft.Extensions.DependencyInjection

Trước tiên phải đảm bảo tích hợp Package Microsoft.Extensions.DependencyInjection vào dự án

dotnet add package Microsoft.Extensions.DependencyInjection

Sau đó sử dụng namespace

using Microsoft.Extensions.DependencyInjection;

Từ đây các đối tượng người sử dụng lớp, các dependency ta gọi chúng là các dịch vụ (service)!

Lớp ServiceCollection

ServiceCollection là lớp triển khai giao diện IServiceCollection nó có chức năng quản lý các dịch vụ (đăng ký dịch vụ – tạo dịch vụ – tự động hóa inject – và các dependency của địch vụ …). ServiceCollection là trung tâm của kỹ thuật DI, nó là thành phần rất quan trọng trong ứng dụng ASP.NET

Các sử dụng cơ bản như sau:

  • Khởi tạo đối tượng người sử dụng ServiceCollection, sau đó đăng ký (lớp) các dịch vụ vào ServiceCollection
  • Từ ServiceCollection phát sinh ra đối tượng người sử dụng ServiceProvider, từ đối tượng người sử dụng này truy vấn lấy ra các dịch vụ cụ thể khi cần.

ServiceLifetime: Mỗi dịch vụ (lớp) khi đăng ký vào ServiceCollection thì có một đối tượng người sử dụng ServiceDescriptor chứa thông tin về dịch vụ đó, địa thế căn cứ vào ServiceDescriptor để ServiceCollection khởi tạo dịch vụ khi cần. Trong ServiceDescriptor có tính chất Lifetime để xác định dịch vụ tạo ra tồn tại trog bao lâu. Lifetime có kiểu ServiceLifetime (kiểu enum) có những giá trị cụ thể:

Scoped 1 Một bản thực thi (instance) của dịch vụ (Class) được tạo ra cho từng phạm vi, tức tồn tại cùng với sự tồn tại của một đối tượng người sử dụng kiểu ServiceScope (đối tượng người sử dụng này tạo bằng phương pháp gọi ServiceProvider.CreateScope, đối tượng người sử dụng này hủy thì dịch vụ cũng trở nên hủy). Singleton 0 Duy nhất một phiên bản thực thi (instance of class) (dịch vụ) được tạo ra cho hết vòng đời của ServiceProvider Transient 2 Một phiên bản của dịch vụ được tạo mọi khi được yêu cầu

Để mở màn sử dụng, khởi tạo ServiceCollection như sau

var services = new ServiceCollection();

Khi đã có đối tượng người sử dụng bạn cũng có thể thực hiện các thao tác như đăng ký dịch vụ vào ServiceCollection (DI container), lấy đối tượng người sử dụng lớp ServiceProvider thông qua đó để truy vấn lấy các dịch vụ …

Một số phương thức của ServiceCollection, trong các phương thức có thông số thì kiểu như sau:

  • ServiceType : Kiểu (tên lớp) dịch vụ
  • ImplementationType : Kiểu (tên lớp) sẽ tạo ra đối tượng người sử dụng dịch vụ theo tên ServiceType, cần đảm bảo ImplementationType là một lớp triển khai / thừa kế từ ServiceType, hoặc đấy là ServiceType

Phương thức Diễn thuyết AddSingletonvàlt;ServiceType, ImplementationTypevàgt;()Nếu ServiceType giống ImplementationType có thể viết AddSingletonvàlt;ServiceTypevàgt;() Đăng ký dịch vụ kiểu Singleton. Ví dụ: services.AddSingletonvàlt;IHorn, HeavyHornvàgt;(); Đăng ký dịch vụ kiểu IHorn, mà khi dịch vụ IHorn được yêu cầu nó tạo ra và trả về đối tượng người sử dụng kiểu HeavyHorn, do là Singleton chỉ một đối tượng người sử dụng của dịch vụ được tạo, nếu đã có yêu cầu sau trả về đối tượng người sử dụng lần trước tạo (gọi ra thế nào ở phần sau). AddTransientvàlt;ServiceType, ImplementationTypevàgt;() Hoặc AddTransientvàlt;ServiceTypevàgt;() Đăng ký dịch vụ thuộc loại Transient, luôn tạo mới mọi khi có yêu cầu lấy dịch vụ. AddScopedvàlt;ServiceType, ImplementationTypevàgt;() Đăng ký vào mạng lưới hệ thống dịch vụ kiểu Scoped BuildServiceProvider() Tạo ra đối tượng người sử dụng lớp ServiceProvider, đối tượng người sử dụng này dùng làm triệu gọi, tạo các dịch vụ thiết lập ở trên.

Các phương thức AddSingleton, AddTransient, AddScoped còn tồn tại bản quá tải mà thông số là một callback delegate tạo đối tượng người sử dụng. Nó là cách triển khai pattern factory

Lớp ServiceProvider

Lớp ServiceProvider cung cấp cơ chế để lấy ra (tạo và inject nếu cần) các dịch vụ đăng ký trong ServiceCollection. Đối tượng người sử dụng ServiceProvider được tạo ra bằng phương pháp gọi phương thức BuildServiceProvider() của ServiceCollection

var serviceprovider = services.BuildServiceProvider();

Một số phương thức trong ServiceProvider

Phương thức Diễn thuyết GetServicevàlt;ServiceTypevàgt;() Lấy dịch vụ có kiểu ServiceType – trả về null nếu dịch vụ không tồn tại // lấy đối tượng người sử dụng triển khai IHorn services.GetServicevàlt;IHornvàgt;() GetRequiredService(ServiceType) Lấy dịch vụ có kiểu ServiceType – phát sinh Exception nếu dịch vụ không tồn tại // lấy đối tượng người sử dụng kiểu Car services.GetRequiredService(Car) CreateScope() Tạo một phạm vi mới, thường dùng khi sử dụng những dịch vụ có sự tác động theo Scoped, sử dụng cơ bản: using (var scope = serviceprovider.CreateScope()) { // Lấy Service trong một pham vi var myservice = scope.ServiceProvider.GetServicevàlt;ServiceTypevàgt;(); // … // … Thoát khỏi phạm vị, các Service kiểu Scoped tạo ra trong phạm vi // using … sẽ bị hùy }

Để cụ thể hơn sử dụng DI với thư viện ServiceCollection, ta sẽ thực hiện cho vài trường hợp:

Sử dụng ServiceCollection cơ bản

Xem Thêm : Macro là gì? Hướng dẫn cách tạo Macro đơn giản, dễ làm

Xét lại các lớp IClassA, IClassB, ClassA … đã xây dựng ở trên, ứng dụng vài trường hợp riêng lẻ sau:

Thương Mại & Dịch Vụ được đăng ký là Singleton

// Đăng ký dịch vụ IClassC tương ứng với đối tượng người sử dụng ClassC services.AddSingletonvàlt;IClassC, ClassCvàgt;(); var provider = services.BuildServiceProvider(); for (int i = 0; i < 5; i++) { var service = provider.GetServicevàlt;IClassCvàgt;(); Console.WriteLine(service.GetHashCode()); } // ClassC is created // 32854180 // 32854180 // 32854180 // 32854180 // 32854180 // Gọi 5 lần chỉ 1 dịch vụ (đối tượng người sử dụng) được tạo ra

Thương Mại & Dịch Vụ được đăng ký là Transient

services.AddTransientvàlt;IClassC, ClassCvàgt;(); var provider = services.BuildServiceProvider(); for (int i = 0; i < 5; i++) { var service = provider.GetServicevàlt;IClassCvàgt;(); Console.WriteLine(service.GetHashCode()); } // ClassC is created // 32854180 // ClassC is created // 27252167 // ClassC is created // 43942917 // ClassC is created // 59941933 // ClassC is created // 2606490 // Gọi 5 lần có 5 dịch vụ được tạo ra

Thương Mại & Dịch Vụ được đăng ký là Scoped

ServiceCollection services = new ServiceCollection(); // Đăng ký dịch vụ IClassC tương ứng với đối tượng người sử dụng ClassC services.AddScopedvàlt;IClassC, ClassCvàgt;(); var provider = services.BuildServiceProvider(); // Lấy dịch vụ trong scope toàn cục for (int i = 0; i < 5; i++) { var service = provider.GetServicevàlt;IClassCvàgt;(); Console.WriteLine(service.GetHashCode()); } // Tạo ra scope mới using (var scope = provider.CreateScope()) { // Lấy dịch vụ trong scope for (int i = 0; i < 5; i++) { var service = scope.ServiceProvider.GetServicevàlt;IClassCvàgt;(); Console.WriteLine(service.GetHashCode()); } } // ClassC is created // 32854180 // 32854180 // 32854180 // 32854180 // 32854180 // ClassC is created // 27252167 // 27252167 // 27252167 // 27252167 // 27252167 // Mỗi scope tạo ra một loại dịch vụ

Kiểm tra tạo và inject các dịch vụ đăng ký trong ServiceCollection

// ClassA // IClassB -> ClassB, ClassB1 // IClassC -> ClassC, ClassC1 ServiceCollection services = new ServiceCollection(); services.AddSingletonvàlt;ClassA, ClassAvàgt;(); services.AddSingletonvàlt;IClassC, ClassCvàgt;(); services.AddSingletonvàlt;IClassB, ClassBvàgt;(); var provider = services.BuildServiceProvider(); ClassA service_a = provider.GetServicevàlt;ClassAvàgt;(); service_a.ActionA(); // ClassC is created // ClassB is created // ClassA is created // kích hoạt in ClassA // kích hoạt in ClassB // kích hoạt in ClassC

Sử dụng Delegate / Factory đăng ký dịch vụ

Sử dụng Delegate đăng ký

Các phương thức để đăng dịch vụ vào ServiceCollection như AddSingleton, AddSingleton, AddTransient còn tồn tại phiên bản (nạp chồng) nó nhận thông số là delegate trả về đối tượng người sử dụng dịch vụ có kiểu ImplementationType. Ví dụ AddSingleton, cú pháp đó là:

services.AddSingletonvàlt;ServiceTypevàgt;((IServiceProvider provider) => { // các thông tư // … return (đối tượng người sử dụng kiểu ImplementationType); });

Trong cú pháp trên thì Delegate đó là

(IServiceProvider provider) => { // các thông tư // … return (đối tượng người sử dụng kiểu ImplementationType); }

Nó nhận thông số là IServiceProvider (đấy là đối tượng người sử dụng được sinh ra bởi ServiceCollection.BuildServiceProvider()), Delegate phải trả về một đối tượng người sử dụng triển khai từ ServiceType

Ví dụ, tạo ra thêm lớp ClassB2

class ClassB2 : IClassB { IClassC c_dependency; string message; public ClassB2(IClassC classc, string mgs) { c_dependency = classc; message = mgs; Console.WriteLine(“ClassB2 is created”); } public void ActionB() { Console.WriteLine(message); c_dependency.ActionC(); } }

Lớp trên khi khởi tạo cần có hai thông số. Vậy khi đăng ký vào dịch vụ Theo phong cách:

services.AddSingletonvàlt;IClassB, ClassB2vàgt;();

Thì thông số khởi tạo IClassC được inject, trong những lúc đó thông số chuỗi string không đăng ký sẽ dẫn tới lỗi. Lúc này còn có thể đăng ký với Delegate và truyền chuỗi khởi tạo cụ thể, ví dụ:

services.AddSingletonvàlt;IClassBvàgt;((IServiceProvider serviceprovider) => { var service_c = serviceprovider.GetServicevàlt;IClassCvàgt;(); var sv = new ClassB2(service_c, “Thực hiện trong ClassB2”); return sv; });

Lúc này nếu lấy ra dịch vụ IClassB (hoặc khi nó Inject vào dịch vụ khác) , nếu dịch vụ đó chưa xuất hiện nó sẽ thi hành Delegate để tạo dịch vụ.

Sử dụng Factory đăng ký

Delegate trên bạn cũng có thể khai báo thành một phương thức, một phương thức cung cấp cơ chế để tạo ra đối tượng người sử dụng mong muốn gọi là Factory.

// Factory nhận thông số là IServiceProvider và trả về đối tượng người sử dụng địch vụ cần tạo public static ClassB2 CreateB2Factory(IServiceProvider serviceprovider) { var service_c = serviceprovider.GetServicevàlt;IClassCvàgt;(); var sv = new ClassB2(service_c, “Thực hiện trong ClassB2”); return sv; }

Lúc này còn có thể sử dụng Factory trên để đăng ký IClassB.

services.AddSingletonvàlt;IClassBvàgt;(CreateB2Factory);

Kỹ thuật DI với thư viện DependencyInjection ở trên là kiến rất cần nắm vững, nó là cơ sở để học các các mô hình lập trình tân tiến, nhất là sau này ứng dụng với Asp.Net Core bạn phải hiểu nó.

You May Also Like

About the Author: v1000