Tóm Tắt
Inversion of Control (IoC) / Dependency inversion
Inversion of Control (IoC – Đảo ngược điều khiển) là một nguyên lý thiết kế trong công nghệ phần mềm
trong đó các thành phần nó dựa vào để làm việc bị đảo ngược quyền điều khiển
khi so sánh với lập trình hướng thủ thục truyền thống.
Khi áp dụng cho các đối tượ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)Bạn đang đọc: Dependency injection (DI) trong C# với ServiceCollection
dependency :
Giả sử bạn có một lớp classA,
lớp này có sử dụng một chức năng từ đối
tượ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 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).
Sự phụ thuộc vào đối tượng người tiêu dùng này vào đối tượng người tiêu dùng khác ở thời gian viết code và thời gian thực thi là trọn vẹn giống nhau .class ClassC { public void ActionC() => Console.WriteLine("Action 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("Action 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("Action 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ả: // Action in ClassA // Action in ClassB // Action in ClassCThiết kế theo cách đảo ngược phụ thuộc Inverse Dependency
Cách viết code này, ở thời điểm 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 nghĩa là kết quả không đổi. Tuy nhiên, khi thiết kế ở thời điểm 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 classA và classB (xem hình)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ó nghĩa tại 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("Action 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("Action 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("Action in ClassA"); b_dependency.ActionB(); } }Kết quả sử dụng khi chạy là tựa như
IClassC objectC = new ClassC(); IClassB objectB = new ClassB(objectC); ClassA objectA = new ClassA(objectB); objectA.ActionA();Code thuận tiện thay những phụ thuộc vào, ví dụ, định nghĩa thêm :
class ClassC1 : IClassC { public ClassC1() => Console.WriteLine ("ClassC1 is created"); public void ActionC() { Console.WriteLine("Action 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("Action in B1"); c_dependency.ActionC(); } }Khi sử dụng, hoàn toàn có thể sửa chữa thay thế những 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();Kỹ thuật lập trình Dependency injection
Dependency injection (DI) là một kỹ thuật trong lập trình, nó là một hình thức cụ thể của
Inverse of Control (Dependency Inverse) đã nói ở trên.
DI thiết kế sao cho các dependency (phụ thuộc) của một đối tượng CÓ THỂ được đưa vào, tiêm vào
đối tượng đó (Injection) khi nó cần tới (khi đối tượng khởi tạo). Cụ thể cần làm:
- Xây dựng các lớp (dịch vụ) có sự phụ thuộc nhau một cách lỏng lẻo, và dependency có thể tiêm vào
đối tượng (injection) – thường qua phương thức khởi tạo constructor, property, setter- Xây dựng được một thư viện có thể tự động tạo ra các đối tượng, các dependency tiêm vào đối tượng đó,
thường là áp dụng kỹ thuật Reflection của C# (xem thêm
lớp type):
Thường là thư viện này quá phức tạp để tự phát triển nên có thể sử dụng các thư viện có sẵn như:Microsoft.Extensions.DependencyInjection hoặc thư viện bên thứ ba như
Windsor,
Unity
Ninject …Nói chung, những khái niệm về DI rất trừu tượng và khó hiểu ! Để rõ hơn cần thực thi từng bước qua những ví dụ .
Giả sử có lớp
Car
có chức năng (phương thức)Beep()
– để phát ra tiếng
còi xe, mà để phát ra tiếng còi – nó lại dựa vào vào lớpHorn
chuyên tạo ra tiếng còi
– lúc đó ta nói lớpCar
có một phụ thuộc (dependency Horn) là lớp Horn, Horn là
dependency của Car.Muốn lớp Car hoạt động giải trí thì nó phải có đối tượng người tiêu dùng ( dịch vụ ) từ Horn. Vậy khi phong cách thiết kế, thường có hai cách :
- Trong lớp Car thiết kế code mà nó phụ thuộc cứng vào lớp Horn – tự khởi tạo Horn,
cách thiết kế này không có khả năng áp dụng kỹ thuật DI- Trong lớp Car, dependency Horn không do Car trực tiếp khởi tạo mà nó được đưa vào qua phương thức khởi tạo,
qua setter, qua gán property. Các thiết kế này linh hoạt và có KHẢ NĂNG để áp dụng DIVí dụ code cho 2 trường hợp này như sau :
Viết Code mà không có khả năng áp dụng DI
public class Horn { public void Beep () => Console.WriteLine ("Beep - beep - beep ..."); } public class Car { public void Beep () { // chức năng Beep xây dựng có định với Horn // tự tạo đối tượng horn (new) và dùng nó Horn horn = new Horn (); horn.Beep (); } }Code viết như trên khá thông dụng và vẫn chạy tốt. Bấm còi xe vẫn kêu Beep, beep …
var car = new Car(); car.Beep(); // Beep - beep - beep ...Nhưng code trên có một vấn đề là tính linh hoạt khi sử dụng. Chức năng
Beep()
củaCar
nó tự tạo ra đối tượngHorn
và sử dụng nó – làm cho
Car gắn cứng vào Horn với cấu trúc khởi tạo hiện thời.Nếu lớp Horn sửa lại, ví dụ muốn khởi tạo Horn phải chỉ ra một tham số nào đó, ví dụ
như độ lớn tiêng còilevel
public class Horn { int level; // độ lớn của còi xe public Horn (int level) => this.level = level; // thêm khởi tạo level public void Beep () => Console.WriteLine ($"(level {level}) Beep - beep - beep ..."); }Việc thay đổi
Horn
làm choCar
không còn dùng được nữa, nếu
muốnCar
hoạt động cần sửa lại code của Car, ví dụ tại Beep sửa thànhHorn horn = new Horn(10); // Khởi tạo với Horn với tham số level horn.Beep();Viết code có KHẢ NĂNG áp dụng DI
Xây dựng lại ví dụ trên sao cho phụ thuộc vào của Car hoàn toàn có thể đưa vào nó từ bên ngoài .
public class Horn { public void Beep () => Console.WriteLine ("Beep - beep - beep ..."); } public class Car { // horn là một Dependecy của Car Horn horn; // dependency Horn được đưa vào Car qua hàm khởi tạo public Car(Horn horn) => this.horn = horn; public void Beep () { // Sử dụng Dependecy đã được Inject horn.Beep (); } }Khi sử dụng :
Horn horn = new Horn(); var car = new Car(horn); // horn inject vào car car.Beep(); // Beep - beep - beep ...Code trên hoạt động giải trí tựa như trường hợp thứ nhất. Bằng cách khai bảo Horn là một biến thành viên trong Car, Car đã có một dependency là đối tượng người dùng lớp Horn, dependency này không phải do Car tạo ra, nó được bơm vào ( phân phối ) trải qua phương pháp khởi tạo của nó .
Kết quả gọi
car.Beep();
có vẻ kết quả vẫn như trên, nhưng code mới này có một số lợi ích.
Ví dụ, nếu sửa cập nhật lại Horn bằng cách sửa phương thức khởi tạo của nó, thì lớp Car không phải sửa gì!public class Horn { int level; // thêm độ lớn còi xe public Horn (int level) => this.level = level; // thêm khởi tạo level public void Beep () => Console.WriteLine ("Beep - beep - beep ..."); }Horn horn = new Horn(10); var car = new Car(horn); // horn inject vào car car.Beep(); // Beep - beep - beep ...Như vậy, viết code mà những dependency hoàn toàn có thể đưa vào từ bên ngoài ( hầu hết qua phương pháp khởi tạo ), giúp cho những dịch vụ tương đối độc lập nhau. Nó là cơ sở để hoàn toàn có thể dùng những Framework tương hỗ DI ( tự động hóa nghiên cứu và phân tích tạo dịch vụ, dependency )
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 loại 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 thông qua hàm khởi tạo (
như đã thực hiện ở ví dụ trên) – tập trung chuyên sâu vào cách này vì thư viện. NET tương hỗ sẵn- Inject thông qua setter: tức các Dependency như là thuộc tính của lớp, sau đó inject bằng gán
thuộc tính cho Depedencyobject.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 định nghĩa các setter quy định trong interfaceTrong ba kiểu Inject thì Inject qua phương pháp khởi tạo rất phổ cập vì tính linh động, mềm dẻo, dễ kiến thiết 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 hợp với thiết kế phụ thuộc lỏng
lẻo giữa các dependency (Dependency Inverse) ở trên.Đầu tiên thiết kế xây dựng một interface là IHorn
public interface IHorn { void Beep (); }Lớp Car được kiến thiết 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 (); }Với cách tiến hành DI bằng phương pháp khởi tạo như vậy, tích hợp với sự nhờ vào lỏng giữa Car và những lớp tiến hành IHorn. Thì sử dụng Car tạo ra những đối tượng người tiêu dùng đơn cử rất linh động và độc lập với nhiều loại đối tượng người dùng tiến hành IHorn, ví dụ thử tạo ra hai loại còi một cái loại lơn và một cái 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 hỗ trợ tốtToàn bộ phần trên là triết lý cơ bản, tiến hành thực tiễn thì cần có một dịch vụ TT gọi là DI Container, tại đó những lớp ( dịch vụ ) Đ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ự kiến thiế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 kiến thiết xây dựng một DI Container riêng, thay vào đó ta sẽ sử dụng những thư viện tương hỗ sẵn cho. NET
DI Container
Mục đích sử dụng DI, để tạo ra những đối tượng người dùng dịch vụ kéo theo là những Dependency của đối tượng người tiêu 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 trị những dependency ) như : Windsor, Unity Ninject, DependencyInjection …
Trong đó DependencyInjection là DI Container
mặc định củaASP.NET Core
, phần này tìm hiểu về DI Container này
Microsoft.Extensions.DependencyInjectionTrước tiên phải bảo vệ tích hợp Package Microsoft. Extensions. DependencyInjection vào dự án Bất Động Sản
dotnet add package Microsoft.Extensions.DependencyInjectionSau đó sử dụng namespace
using Microsoft.Extensions.DependencyInjection;Từ đây những đối tượng người tiêu dùng lớp, những dependency ta gọi chúng là những dịch vụ ( service ) !
Lớp ServiceCollection
ServiceCollection
là lớp triển khai giao diệnIServiceCollection
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 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.NETCác sử dụng cơ bản như sau :
- Khởi tạo đối tượng
ServiceCollection
,
sau đó đăng ký (lớp) các dịch vụ vàoServiceCollection
- Từ ServiceCollection phát sinh ra đối tượng
ServiceProvider
, từ đối tượ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ượngServiceDescriptor
chứa thông tin về dịch vụ đó, căn cứ vào ServiceDescriptor để
ServiceCollection khởi tạo dịch vụ khi cần. Trong ServiceDescriptor có thuộc tính Lifetime
để xác định dịch vụ tạo ra tồn tại trog bao lâu. Lifetime có kiểuServiceLifetime
(kiểu enum)
có các 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 mỗi 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 kiểuServiceScope
(đối tượng này tạo bằng cách gọiServiceProvider.CreateScope
,
đối tượng này hủy thì dịch vụ cũng bị 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 ServiceProviderTransient
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 bạn 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 lớpServiceProvider
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ó tham 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 dịch vụ theo tênServiceType
, cần đảm bảo ImplementationType là một lớp
triển khai / kế thừa từ ServiceType, hoặc chính là ServiceType
Phương thức Diễn giả AddSingleton
() Nếu ServiceType giống ImplementationType có thể viết
AddSingleton
() Đăng ký dịch vụ kiểu Singleton
. Ví dụ:services.AddSingleton(); Đă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 kiểuHeavyHorn
,
do là Singleton chỉ một đối tượng của dịch vụ được tạo, nếu đã có
yêu cầu sau trả về đối tượng lần trước tạo (gọi ra thế nào ở phần sau).AddTransient
()
Hoặc
AddTransient
() Đă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ụ.AddScoped
() Đăng ký vào hệ thống dịch vụ kiểu Scoped
BuildServiceProvider()
Tạo ra đối tượng lớp ServiceProvider
, đối tượng này dùng để 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 có bản quá tải mà
tham số là một callback delegate tạo đối tượng. Nó là cách triển khai pattern factoryLớ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 ServiceProvider được tạo ra bằng cách gọi phương thứcBuildServiceProvider()
củaServiceCollection
var serviceprovider = services.BuildServiceProvider();Một số phương pháp trong ServiceProvider
Phương thức Diễn giả GetService
() 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 triển khai IHorn services.GetService() 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 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ự ảnh hưở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.GetService(); // ... // ... Ra khỏi phạm vị, các Service kiểu Scoped tạo ra trong phạm vi // using ... sẽ bị hùy } Để đơn cử hơn sử dụng DI với thư viện ServiceCollection, ta sẽ thực hành thực tế cho vài trường hợp :
Sử dụng ServiceCollection cơ bản
Xét lại những lớp IClassA, IClassB, ClassA … đã thiết kế xây dựng ở trên, vận dụng vài trường hợp riêng không liên quan gì đến nhau sau :
Dịch vụ được đăng ký là Singleton
// Đăng ký dịch vụ IClassC tương ứng với đối tượng ClassC services.AddSingleton(); var provider = services.BuildServiceProvider(); for (int i = 0; i < 5; i++) { var service = provider.GetService (); 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) được tạo ra Dịch vụ được đăng ký là Transient
services.AddTransient(); var provider = services.BuildServiceProvider(); for (int i = 0; i < 5; i++) { var service = provider.GetService (); 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 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 ClassC services.AddScoped(); 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.GetService (); 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.GetService (); 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.AddSingleton(); services.AddSingleton (); services.AddSingleton (); var provider = services.BuildServiceProvider(); ClassA service_a = provider.GetService (); service_a.ActionA(); // ClassC is created // ClassB is created // ClassA is created // Action in ClassA // Action in ClassB // Action 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
có phiên bản (nạp chồng) nó nhận tham số là delegate trả về đối tượng dịch vụ có kiểu ImplementationType.
Ví dụ AddSingleton, cú pháp đó là:services.AddSingleton((IServiceProvider provider) => { // các chỉ thị // ... return (đối tượng kiểu ImplementationType); }); Trong cú pháp trên thì Delegate đó là
(IServiceProvider provider) => { // các chỉ thị // ... return (đối tượng kiểu ImplementationType); }Nó nhận tham số là
IServiceProvider
(chính là đối tượng được sinh ra bởi
ServiceCollection.BuildServiceProvider()), Delegate phải trả về một đối tượng triển khai từ
ServiceTypeVí 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 tham số. Vậy khi ĐK vào dịch vụ theo cách :
services.AddSingleton(); Thì tham số khởi tạo IClassC được inject, trong khi đó tham số chuỗi string không ĐK sẽ dẫn tới lỗi. Lúc này hoàn toàn có thể ĐK với Delegate và truyền chuỗi khởi tạo đơn cử, ví dụ :
services.AddSingleton((IServiceProvider serviceprovider) => { var service_c = serviceprovider.GetService (); 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 có nó sẽ thi hành Delegate để tạo dịch vụ.Sử dụng Factory đăng ký
Delegate trên bạn hoàn toàn có thể khai báo thành một phương pháp, một phương pháp phân phối chính sách để tạo ra đối tượng người dùng mong ước gọi là Factory .
// Factory nhận tham số là IServiceProvider và trả về đối tượng địch vụ cần tạo public static ClassB2 CreateB2Factory(IServiceProvider serviceprovider) { var service_c = serviceprovider.GetService(); var sv = new ClassB2(service_c, "Thực hiện trong ClassB2"); return sv; } Lúc này hoàn toàn có thể sử dụng Factory trên để ĐK IClassB .
services.AddSingleton(CreateB2Factory); Sử dụng Options khởi tạo dịch vụ trong DI
Khi một dịch vụ ĐK trong DI, nếu nó cần những tham số để khởi tạo thì ta hoàn toàn có thể Inject những tham số khởi tạo là những đối tượng người dùng như cách làm ở trên. Tuy nhiên để tách bạch giữa những dịch vụ và những thiết lập truyền vào để khởi tạo dịch vụ thì trong ServiceCollection tương hỗ sử dụng giao diện IOptions .
Trước tiên cần thêm package
Microsoft.Extensions.Options
dotnet add package Microsoft.Extensions.OptionsSử dụng namepace :
using Microsoft.Extensions.Options;Các thiết lập cho một dịch vụ thường thiết kế là một lớp chứa các thuộc tính, ví dụ có lớp là
MyService
, khi nó khởi tạo thì cần một đối tượng lớpMyServiceOptions
public class MyServiceOptions { public string data1 { get; set; } public int data2 { get; set; } }Để hoàn toàn có thể Inject MyServiceOptions vào MyService theo nguyên tắc của ServiceCollection thì lớp MyService phong cách thiết kế sử dụng IOption làm tham số khởi tạo như sau :
public class MyService { public string data1 { get; set; } public int data2 { get; set;} // Tham số khởi tạo là IOptions, các tham số khởi tạo khác nếu có khai báo như bình thường public MyService(IOptionsoptions) { // Đọc được MyServiceOptions từ IOptions MyServiceOptions opts = options.Value; data1 = opts.data1; data2 = opts.data2; } public void PrintData() => Console.WriteLine($"{data1} / {data2}"); } Khi tham số khởi tạo có kiểu IOptions, thì nó được Inject vào từ một tập hợp các IOptions riêng biệt
với các dịch vụ, và các IOptions nạp vào ServiceCollection bằng phương thức Configure, cách làm
như sau:Để đăng ký vào hệ thống một lớp
T
là cấu hình,
sau này sẽ có các đối tượng kiểuIOptions
có thể Inject vào thì làm như sau:services.Configure( (T options) { // T là tên lớp chứa các thiết lập // Hãy thiết lập các giá trị cho options } ); Cụ thể đăng ký
MyServiceOptions
tạo ra đối tượng cụ thể
để sau này có thể Inject vào MyService:services.Configure( options => { options.data1 = "Xin chao cac ban"; options.data2 = 2021; } ); Lúc này sử dụng
services.AddSingleton(); var provider = services.BuildServiceProvider(); var myservice = provider.GetService (); myservice.PrintData(); // Kết quả: // Xin chao cac ban / 2021 Như vậy DI Container, đã giúp tạo Config ( đối tượng người dùng MyServiceOptions ) và Inject nó cho dịch vụ khi tạo ( dịch vụ MyService )
Lưu ý 1: nếu muốn lấy đối tượng lớp
MyServiceOptions
trong DI Container, thì:var config = serviceprovider.GetService>() MyServiceOptions myServiceOptions = config.Value; Lưu ý 2: nếu muốn tạo trực tiếp đối tượng
IOptions
,
dành cho trường hợp muốn tạoMyService
trực tiếp không thông qua DI Container. Thì dùng
phương thức FactoryOptions.Create(obj)
, ví dụ:var opts = Options.Create(new MyServiceOptions() { data1 = "DATA-DATA-DATA-DATA-DATA", data2 = 12345 }); MyService myService = new MyService(opts); myService.ShowData();Sử dụng cấu hình từ File cho DI Container
Ở ví dụ trên, các giá trị dữ liệu trong
MyServiceOptions
(nhưdata1
,data2
) có thể lưu ở file sau đó nạp vào khi chương
trình thực thi. Các file cấu hình này hỗ trợ nhiều định dạng như XML, INI, JSON …
(cần cài đặt gói tương ứng)Trước tiên thêm package Microsoft. Extensions. Configuration và Microsoft. Extensions. Options. ConfigurationExtensions
dotnet add package Microsoft.Extensions.Configuration dotnet add package Microsoft.Extensions.Options.ConfigurationExtensionsSau đó, muốn dùng định dạng nào thì thêm Package tương ứng, ví dụ dùng JSON :
dotnet add package Microsoft.Extensions.Configuration.Json dotnet add package Microsoft.Extensions.Configuration.Ini dotnet add package Microsoft.Extensions.Configuration.XmlSử dụng namespace với kiểu file Json
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json;ConfigurationBuilder
Lớp ConfigurationBuilder, giúp nạp các cấu hình lưu trong file config,
từ đó build ra đối tượngConfigurationRoot
, đối tượng này truy cập đến các cấu hình bằng
chỉ toán tử chỉ số[key]
Giá sử cấu hình lưu tại file
appsettings.json
, thì nạp cấu hình đó để có đượcConfigurationRoot
var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) // file config ở thư mục hiện tại .AddJsonFile("appsettings.json"); // nạp config định dạng JSON var configurationroot = configBuilder.Build(); // Tạo configurationrootKhi có đối tượng
ConfigurationRoot
, lấy một Section nào đó bằng phương thứcGetSection(key)
,
nó trả về đối tượng biểu diễn nút cấu hình (JSON), giá trị của nút truy cập bằng thuộc tínhValue
Ví dụ, tạo file
appsettings.json
với nội dung{ "MyServiceOptions" : { "data1" : "ABCDE", "data2" : 123456 }, "Option2" : { "key1" : "Test", "Key2" : 789 } }Truy cập config
var cf1 = configurationroot.GetSection("Option2").GetSection("key1").Value; // Test var cf2 = configurationroot.GetSection("Option2").GetSection("key2").Value; // 789 var cf3 = configurationroot.GetSection("Option2").GetSection("key3").Value; // null, không tồn tạiNhư vậy đã hoàn toàn có thể nạp và đọc những giá trị lưu trong file thông số kỹ thuật json .
Nạp config json vào IOption
Trong file JSON có một Section có tên
MyServiceOptions
,
ta có thể gán các giá trị trong Section đó vào MyServiceOptions trong ServiceCollection bằng cách// Nạp mở phương thức mở rộng services.AddOptions(); services.Configure(configurationroot.GetSection("MyServiceOptions")); // Lưu ý: phải cài package ConfigurationExtensions // dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions Hoàn thiện ví dụ trên
var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) // file config ở thư mục hiện tại .AddJsonFile("appsettings.json"); // nạp config định dạng JSON var configurationroot = configBuilder.Build(); // Tạo configurationroot ServiceCollection services = new ServiceCollection(); services.AddOptions(); services.Configure(configurationroot.GetSection("MyServiceOptions")); services.AddSingleton (); var provider = services.BuildServiceProvider(); var myservice = provider.GetService (); myservice.PrintData(); // Kết quả: // ABCDE / 123456 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 hiện đại, nhất là sau này áp dụng với Asp.Net Core bạn cần hiểu nó.ĐĂNG KÝ KÊNH, XEM CÁC VIDEO TRÊN XUANTHULAB
Đăng ký nhận bài viết mới
Source: https://final-blade.com
Category : Kiến thức Internet