ASP.NET Core框架建立在一個依賴注入框架之上,已注入的方式消費服務已經成為了ASP.NET Core基本的編程模式。為了使讀者能夠更好地理解原生的注入框架框架,我按照類似的設計創建了一個簡易版本的依賴注入框架,并它命名為“Cat”。本篇提供的四個實例主要體現了針對Cat的用法,《一個Mini版的依賴注入框架》提供了針對設計和實現原理的介紹。[本文節選《ASP.NET Core 6框架揭秘》第2章]
[201]模擬容器Cat-普通服務的注冊和提取(源代碼)
[202]模擬容器Cat-針對泛型服務類型的支持(源代碼)
[203]模擬容器Cat-為同一類型提供多個服務注冊(源代碼)
[204]模擬容器Cat-服務實例的生命周期(源代碼)
[201]模擬容器Cat-普通服務的注冊和提取
我們定義了如下所示的接口和對應的實現類型來演示針對Cat的服務注冊。Foo、Bar、Baz和Qux分別實現了對應的接口IFoo、IBar、IBaz和IQux,其中Qux類型上標注的MapToAttribute特性注冊了與對應接口IQux之間的映射。四個類型派生于的基類Base實現了IDisposable接口,我們在其構造函數和實現的Dispose方法中輸出相應的文本,以確定對應的實例何時被創建和釋放。我們還定義了一個泛型的接口IFoobar<T1, T2>和對應的實現類Foobar<T1, T2>,用來演示Cat針對泛型服務實例的提供。
public?interface?IFoo?{}
public?interface?IBar?{}
public?interface?IBaz?{}
public?interface?IQux?{}
public?interface?IFoobar<T1,?T2>?{}public?class?Base?:?IDisposable
{public?Base()???=>?Console.WriteLine($"Instance?of?{GetType().Name}?is?created.");public?void?Dispose()??=>?Console.WriteLine($"Instance?of?{GetType().Name}?is?disposed.");
}public?class?Foo?:?Base,?IFoo{?}
public?class?Bar?:?Base,?IBar{?}
public?class?Baz?:?Base,?IBaz{?}
[MapTo(typeof(IQux),?Lifetime.Root)]
public?class?Qux?:?Base,?IQux?{?}
public?class?Foobar<T1,?T2>:?IFoobar<T1,T2>
{public?T1?Foo?{?get;?}public?T2?Bar?{?get;?}public?Foobar(T1?foo,?T2?bar){Foo?=?foo;Bar?=?bar;}
}
Lifetime是一個代表服務實例生命周期的枚舉,它代表的三種生命周期模式定義如下。
public?enum?Lifetime
{Root,Self,Transient
}
如下所示的代碼片段創建了一個Cat對象,并采用上面提到的方式針對接口IFoo、IBar和IBaz注冊了對應的服務,它們采用的生命周期模式分別為Transient、Self和Root。另外,我們還調用了另一個將當前入口程序集作為參數的Register方法,該方法會解析指定程序集中標注了MapToAttribute特性的類型并進行批量服務注冊。對于我們演示的程序來說,該方法會完成針對IQux/Qux類型的服務注冊。接下來我們利用Cat對象創建了它的兩個子容器,并調用子容器的GetService<T>方法來提供相應的服務實例。
using?App;var?root?=?new?Cat().Register<IFoo,?Foo>(Lifetime.Transient).Register<IBar>(_?=>?new?Bar(),?Lifetime.Self).Register<IBaz,?Baz>(Lifetime.Root).Register(typeof(Foo).Assembly);
var?cat1?=?root.CreateChild();
var?cat2?=?root.CreateChild();void?GetServices<TService>(Cat?cat)?
where?TService?:?class
{cat.GetService<TService>();cat.GetService<TService>();
}GetServices<IFoo>(cat1);
GetServices<IBar>(cat1);
GetServices<IBaz>(cat1);
GetServices<IQux>(cat1);
Console.WriteLine();
GetServices<IFoo>(cat2);
GetServices<IBar>(cat2);
GetServices<IBaz>(cat2);
GetServices<IQux>(cat2);
上面的程序運行之后會在控制臺上輸出圖1所示的結果。由于服務IFoo被注冊為Transient服務,所以Cat針對四次請求都會創建一個全新的Foo對象。IBar服務的生命周期模式為Self,對于同一個Cat只會創建一個Bar對象,所以整個過程中會創建兩個Bar對象。IBaz和IQux服務采用Root生命周期,所以同根的兩個Cat對象提供的其實是同一個Baz/Qux對象。
圖1Cat按照服務注冊對應的生命周期模式提供服務實例
[202]模擬容器Cat-針對泛型服務類型的支持
Cat同樣可以提供泛型服務實例。如下面的代碼片段所示,在為創建的Cat對象添加了針對IFoo和IBar接口的服務注冊之后,我們調用Register方法注冊了針對泛型定義IFoobar<,>的服務注冊,具體的實現類型為Foobar<,>。當我們利用Cat對象提供一個類型為IFoobar<IFoo, IBar>的服務實例時,它會創建并返回一個Foobar<Foo, Bar>對象。
using?App;using?System.Diagnostics;var?cat?=?new?Cat().Register<IFoo,?Foo>(Lifetime.Transient).Register<IBar,?Bar>(Lifetime.Transient)????.Register(typeof(IFoobar<,>),?typeof(Foobar<,>),?Lifetime.Transient);
var?foobar?=?(Foobar<IFoo,?IBar>?)cat.GetService<IFoobar<IFoo,?IBar>>();
Debug.Assert(foobar?.Foo?is?Foo);
Debug.Assert(foobar?.Bar?is?Bar);
[203]模擬容器Cat-為同一類型提供多個服務注冊
我們可以為同一個類型提供多個服務注冊。雖然添加的所有服務注冊均是有效的,但由于GetService<TService>擴展方法總是返回一個服務實例,我們對該方法應用了“后來居上”的策略,即采用最近添加的服務注冊創建服務實例。另一個GetServices<TService>擴展方法將返回根據所有服務注冊提供的服務實例。下面的代碼片段為創建的Cat對象添加了三個針對Base類型的服務注冊,對應的實現類型分別為Foo、Bar和Baz。我們調用了Cat對象的GetServices<Base>方法返回包含三個Base對象的集合,集合元素的類型分別為Foo、Bar和Baz。
using?App;
using?System.Diagnostics;var?services?=?new?Cat().Register<Base,?Foo>(Lifetime.Transient).Register<Base,?Bar>(Lifetime.Transient).Register<Base,?Baz>(Lifetime.Transient).GetServices<Base>();
Debug.Assert(services.OfType<Foo>().Any());
Debug.Assert(services.OfType<Bar>().Any());
Debug.Assert(services.OfType<Baz>().Any());
[204]模擬容器Cat-服務實例的生命周期
如果提供服務實例的類型實現了IDisposable接口,我們必須在適當的時候調用其Dispose方法釋放它。由于服務實例的生命周期完全由作為依賴注入容器的Cat對象來管理,所以通過調用Dispose方法針對服務實例的釋放也由它負責。Cat對象針對提供服務實例的釋放策略取決于采用的生命周期模式,具體的策略如下。
Transient和Self:所有實現了IDisposable接口的服務實例會被當前Cat對象保存起來,當Cat對象自身的Dispose方法被調用的時候,這些服務實例的Dispose方法會隨之被調用。
Root:由于服務實例保存在作為根容器的Cat對象上,所以當作為根的Cat對象的Dispose方法被調用的時候,這些服務實例的Dispose方法會隨之被調用。
上述釋放策略可以通過如下演示實例來印證。如下代碼片段所示,我們創建了一個Cat對象并添加了相應的服務注冊。我們調用它的CreateChild方法創建了代表子容器的Cat對象,并用它提供了四個注冊服務對應的實例。
using?App;
using?(var?root?=?new?Cat().Register<IFoo,?Foo>(Lifetime.Transient).Register<IBar>(_?=>?new?Bar(),?Lifetime.Self).Register<IBaz,?Baz>(Lifetime.Root).Register(typeof(IFoo).Assembly))
{????using?(var?cat?=?root.CreateChild()){cat.GetService<IFoo>();cat.GetService<IBar>();cat.GetService<IBaz>();cat.GetService<IQux>();Console.WriteLine("Child?cat?is?disposed.");}Console.WriteLine("Root?cat?is?disposed.");
}
由于兩個Cat對象的創建都是在using塊中進行的,所以它們的Dispose方法都會在using塊結束的地方被調用。該程序運行之后會在控制臺上輸出圖2所示的結果,我們可以看到當作為子容器的Cat對象的Dispose方法被調用時,由它提供的兩個生命周期模式分別為Transient和Self的服務實例(Foo和Bar)被正常釋放。而生命周期模式為Root的服務實例(Baz和Qux對象)的Dispose方法會延遲到作為根容器的Cat對象的Dispose方法被調用的時候。
圖2 服務實例的釋放
《ASP.NET Core 6框架揭秘》實例演示[01]:編程初體驗
《ASP.NET Core 6框架揭秘》實例演示[02]:基于路由、MVC和gRPC的應用開發
《ASP.NET Core 6框架揭秘》實例演示[03]:Dapr初體驗