在對ASP.NET Core管道中關於依賴註入的兩個核心對象(ServiceCollection和ServiceProvider)有了足夠的認識之後,我們將關註的目光轉移到編程層面。在ASP.NET Core應用中基於依賴註入的編程主要涉及到兩個方面,它們分別是將服務註冊到ServiceCollec... ...
在對ASP.NET Core管道中關於依賴註入的兩個核心對象(ServiceCollection和ServiceProvider)有了足夠的認識之後,我們將關註的目光轉移到編程層面。在ASP.NET Core應用中基於依賴註入的編程主要涉及到兩個方面,它們分別是將服務註冊到ServiceCollection中,和採用註入的方式利用ServiceProvider提供我們所需的服務。我們先來討論ASP.NET Core應用中如何進行服務註冊。[本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、服務註冊
系統自動註冊的服務
手工註冊的服務
二、以註入的形式提取服務
啟動類型的構造函數和Configure方法種註入服務
中間件類型的構造函數和Invoke方法中註入服務
Controller類型的構造函數中註入服務
View中註入服務
三、與第三方DI框架的整合
一、服務註冊
就註冊的主體來劃分,ASP.NET Core應用中註冊的服務大體可以分為兩種類型,一種是WebHostBuilder在創建WebHost之前自動註冊的服務,這些服務確保了後續管道能夠順利構建並能提供基本的請求處理能力。另一種則是用戶根據自身的需要註冊的,如果系統自動註冊的服務不符合我們的需求,我們也可以註冊自己的服務來覆蓋它。
系統自動註冊的服務
那麼系統在構建ASP.NET Core管道的時候到底自行註冊了那些服務呢?對於這個問題,我們不用去查看相關的源代碼如何編寫,而只需要編寫如下一個簡單的程式就可以將這些服務輸出來。
1: public class Program
2: {
3: public static void Main()
4: {
5: Console.WriteLine("{0,-30}{1,-15}{2}", "Service Type", "Lifetime", "Implementation");
6: Console.WriteLine("-------------------------------------------------------------");
7:
8: new WebHostBuilder()
9: .UseKestrel()
10: .Configure(app => { })
11: .ConfigureServices(svcs =>
12: {
13: IServiceProvider serviceProvider = svcs.BuildServiceProvider();
14: foreach (var svc in svcs)
15: {
16: if (null != svc.ImplementationType)
17: {
18: Console.WriteLine("{0,-30}{1,-15}{2}", svc.ServiceType.Name, svc.Lifetime, svc.ImplementationType.Name);
19: continue;
20: }
21: object instance = serviceProvider.GetService(svc.ServiceType);
22: Console.WriteLine("{0,-30}{1,-15}{2}", svc.ServiceType.Name, svc.Lifetime, instance.GetType().Name);
23: }
24:
25: })
26: .Build();
27: }
28: }
如上面的代碼片斷所示,我們利用WebHostBuilder創建了一個WebHost對象。在此之前,我們調用擴展方法UseKestrel註冊了一個KestrelServer類型的伺服器,指定一個空的Action<IApplicationBuilder>對象作為參數調用了它的Configure方法,我們只得到這樣的方法調用會創建了一個DelegateStartup對象。我們直接利用ConfigureServices方法得到所有自動註冊的服務,並列印出每個服務的註冊類型、生命周期模式和實現類型。當我們運行這個程式之後,控制臺上將列印出如下圖所示的服務列表。對於列出的這些服務,我們是不是看到很多熟悉的身影?
手工註冊的服務
如果具體的項目需要採用依賴註入的方式來完成一些業務功能的實現,那就需要在應用初始化的過程中手工註冊相應的服務。初次之外,我們也可以採用手工註冊服務的方式來覆蓋系統自動註冊的服務。總的來說,我們可以採用種方式實現對服務的手工註冊,其中一種就是按照如下的形式調用WebHostBuilder的ConfigureServices方法來註冊服務,而另一種則是將服務註冊實現在啟動類的ConfigureServices方法中。
註冊方式1:
1: new WebHostBuilder()
2: .ConfigureServices(svcs => svcs
3: .AddTransient<IFoo, Foo>()
4: .AddScoped<IBar, IBar>()
5: .AddSingleton<IBaz, Baz>())
6: …
註冊方式2:
1: public class Startup
2: {
3: public void ConfigureServices(IServiceCollection svcs)
4: {
5: svcs.AddTransient<IFoo, Foo>()
6: .AddScoped<IBar, IBar>()
7: .AddSingleton<IBaz, Baz>();
8: }
9: …
10: }
通過前面的介紹,我們知道這兩種方式真正執行服務註冊的時機是不同的。第一種形式的服務註冊發生在WebHostBuilder創建WebHost之前,包含這些服務的ServiceCollection以及由此創建的ServiceProvider將直接提供給後續創建的WebHost。而第二種形式的服務註冊則發生在WebHost初始化過程中,實際上是藉助一個ConventionBasedStartup對象來完成的。
二、以註入的形式提取服務
依賴註入的最終目錄在於實現以註入的形式來消費預先註冊的服務。在一個ASP.NET Core應用中,我們在很多地方都可以採用這種編程方式,我們在前一章中對此也有所提及。經過我的總結,我們常用的依賴註入編程主要應用在如下幾個方面:
- 啟動類型的構造函數和Configure方法中定義相應參數以註入的形式獲取註冊的服務。
- 中間件類型的構造函數和Invoke方法定義任何參數以註入的形式獲取註冊的服務。
- ASP.NET Core MVC應用中Controller類型的構造函數中定義任何參數以註入的形式獲取註冊的服務。
- ASP.NET Core MVC應用的View中通過@inject指令直接獲取註冊的服務。
啟動類型的構造函數和Configure方法種註入服務
當我們在定義啟動類型的時候,通過調用WebHostBuilder的ConfigureServices方法註冊的服務可以在啟動類的構造函數中進行註入,而啟動類的Configure方法不但可以註入調用WebHostBuilder的ConfigureServices方法註冊的服務,也可以註入自身ConfigureServices方法註冊的服務。如下所示的代碼片斷展示了一個比較典型的例子。
1: new WebHostBuilder()
2: .UseKestrel()
3: .ConfigureServices(svcs => svcs
4: .AddSingleton<IFoo, Foo>()
5: .AddSingleton<IBar, Bar>())
6: .UseStartup<Startup>()
7: …
8:
9: public class Startup
10: {
11: public Startup(IFoo foo, IBar bar)
12: {
13: Debug.Assert(typeof(Foo).IsInstanceOfType(foo));
14: Debug.Assert(typeof(Bar).IsInstanceOfType(bar));
15: }
16:
17: public void ConfigureServices(IServiceCollection svcs)
18: {
19: svcs.AddTransient<IBaz, Baz>()
20: .AddTransient<IGux, Gux>();
21: }
22:
23: public void Configure(IApplicationBuilder app, IFoo foo, IBar bar, IBaz baz, IGux gux)
24: {
25: Debug.Assert(typeof(Foo).IsInstanceOfType(foo));
26: Debug.Assert(typeof(Bar).IsInstanceOfType(bar));
27: Debug.Assert(typeof(Baz).IsInstanceOfType(baz));
28: Debug.Assert(typeof(Gux).IsInstanceOfType(gux));
29: }
30: }
中間件類型的構造函數和Invoke方法中註入服務
當我們按照約定定義中間件類型的時候,我們可以在構造函數定義相應的參數來註入通過任何形式註冊的服務。如下麵的代碼片斷所示,中間件類型的構造函數和Invoke方法都定義了相應的參數來以註入的形式和獲取通過調用WebHostBuilder的ConfigureServices方法註冊的兩個服務。
1: new WebHostBuilder()
2: .UseKestrel()
3: .ConfigureServices(svcs => svcs
4: .AddSingleton<IFoo, Foo>()
5: .AddSingleton<IBar, Bar>())
6: .Configure(app=>app.UseMiddleware<FoobarMiddleware>())
7: ...
8:
9: public class FoobarMiddleware
10: {
11: private RequestDelegate _next;
12: public FoobarMiddleware(RequestDelegate next, IFoo foo, IBar bar)
13: {
14: _next = next;
15: Debug.Assert(typeof(Foo).IsInstanceOfType(foo));
16: Debug.Assert(typeof(Bar).IsInstanceOfType(bar));
17: }
18:
19: public async Task Invoke(HttpContext context, IFoo foo, IBar bar)
20: {
21: Debug.Assert(typeof(Foo).IsInstanceOfType(foo));
22: Debug.Assert(typeof(Bar).IsInstanceOfType(bar));
23: await _next(context);
24: }
25: }
Controller類型的構造函數中註入服務
在ASP.NET Core MVC應用中,我們經常在Controller類型的構造函數定義相應的參數來以註入的方式獲取預先註冊的服務。如下所示的這個HomeController就採用構造器註入的方式獲取通過調用WebHostBuilder的ConfigureServices方法註冊的兩個服務。
1: new WebHostBuilder()
2: .UseKestrel()
3: .ConfigureServices(svcs => svcs