上回介紹了 ASP.NET vNext 內建容器的基本用法,這次要試試把 ASP.NET vNext 的內建容器換成 Autofac。

這一次,在撰寫範例程式的過程中,光是解決 KRE 與相關套件的版本不合的問題,就花了我個把小時。所以還是得先提醒一下,目前 ASP.NET vNext 還不是正式版,所以本文的操作步驟與程式碼不一定符合你的開發環境。
如果你有興趣了解我最後一個碰到的問題,以及最後是怎麼解決的。可以看這帖:https://github.com/aspnet/Home/issues/218(至於更早之前碰到的問題就略過不提了。不重要,因為是發生在比較早的 beta-1 版本。

本文範例所使用的開發環境:
  • Windows Server 2012 R2
  • Visual Studio 2015 Preview
  • KRE-CLR-x86.1.0.0-rc1-10798

小引

上回提到,ASP.NET vNext 的內建容器支援四種生命週期模式:Instance、Singleton、Transient、Scoped。而且,上一次的範例程式也示範了內建容器的基本用法。這一次要試試把 ASP.NET vNext 的內建容器換成 Autofac。

在此之前,先補充一點基本觀念。

在 ASP.NET vNext 之前,.NET Framework 對 DI(dependency injection)的支援並不那麼徹底,比較像是附加功能。到了 ASP.NET vNext ,DI(dependency injection)搖身一變,已成為一級公民。明確地說,現在不僅內建了一個小巧的 DI 容器,同時也提供了適度的彈性,能夠與其他第三方容器銜接。不過,內建的 DI 容器比較陽春,無法滿足某些需求,例如欲解析之類別有多個建構函式時,內建的 DI 容器無法讓我們指定使用特定建構函式。像這類更細緻的控制,還是得靠其他第三方 DI 框架才行。

ASP.NET vNext 的內建 DI 容器

ASP.NET vNext 之所以能夠搭配其他 DI 框架一起使用,是因為它在 DI 容器實作之上提供了一個抽象層。具體來說,這個抽象層就是 System.IServiceProvider 介面,它定義了內建 DI 容器應該具備哪些功能。.NET Framework 的其他元件(如 MVC、路由、SignalR、Entity Framework 等)都支援這個介面,而且也只會使用這個介面所定義的 DI 相關功能。所以基本上,ASP.NET vNext 的內建 DI 容器就等於是個全域的 Service Locator

不過,這並不代表你的應用程式也受限於此介面——你可以寫一個自訂的類別來封裝你慣用的 DI 容器,然後把內建的 IServiceProvider 元件換掉。如此一來,所有服務解析的工作就會交給你指定的自訂容器來處理。此外,你也可以讓自訂容器只解析特定類型的服務,而把其他不需要特別處理的服務類型丟回(fallback)給內建的容器來解析。
在 ASP.NET vNext 中,由於所有的內部框架/元件都是透過同一個容器來註冊服務,相依物件便更容易跨越框架邊界(四處流竄?),注入至以往不容易到達的地方。

內建的 DI 容器支援下列幾種生命週期:
  • Instance:解析特定服務類型時,總是傳回由你自行建立的特定物件。
  • Transient:每次解析時都建立新的物件。
  • Singleton:每次解析時都傳回先前已建立的同一個物件。該物件等於是全域(對整個容器而言)共享的單一物件。
  • Scoped:針對特定範圍共享同一個物件。作用等同於特定範圍內的 Singleton。

接著要來試試把 Autofac 加入至 ASP.NET vNext 的 DI 框架。

BYOC to ASP.NET vNext

前面提過,ASP.NET vNext 允許你使用自訂容器來取代內建的 DI 容器,但不必是完全取代,而是可以讓 ASP.NET 優先用你提供的容器,而讓內建的容器退居二線,擔任「備援」的角色。正因為如此,我們通常不說「抽換內建容器」,而說「把你的容器加入 ASP.NET vNext」,亦即 BYOC (Bring Your Own Container) to ASP.NET vNext。

延續上回的範例程式,原本的 Startup 類別是這麼寫:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddScoped<ITimeService, TimeService>(); 
    }
}

其中 ConfigureServices 方法使用了內建容器來註冊 TimeService 服務。這個部分要改用 Autofac 來做。

註:ASP.NET vNext 框架會先呼叫 Startup 類別的 ConfigureServices 方法,然後再呼叫 Configure 方法。

第一步,為專案加入必要的組件參考。這個部分可直接修改 project.json,如下所示:

{
  "webroot": "wwwroot",
  "version": "1.0.0-*",
  "exclude": [
    "wwwroot"
  ],
  "packExclude": [
    "**.kproj",
    "**.user",
    "**.vspscc"
  ],
  "dependencies": {
    "Microsoft.AspNet.Server.IIS": "1.0.0.0-rc1-10790",
    "Microsoft.AspNet.Mvc": "6.0.0.0-rc1-12170",
    "Microsoft.Framework.DependencyInjection": "1.0.0.0-rc1-10655",
    "Microsoft.Framework.DependencyInjection.Autofac": "1.0.0.0-rc1-10655"
  },
  "frameworks": {
    "aspnet50": {
      "dependencies": {}
    }
  }
}

提醒:dependencies 區塊中的各組件的版本號碼可能會決定這個簡單的範例程式要花你三分鐘還是三小時才能完成。(等到 ASP.NET vNext 發布正式版本之後應該就不會有這些狀況了)

接著修改 Startup 類別的 ConfigureServices 方法,改成這樣:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    
    var builder = new ContainerBuilder();
    builder.Populate(services); 

    builder.RegisterType<TimeService>().As<ITimeService>();

    IContainer container = builder.Build();           
    return container.Resolve<IServiceProvider>();
}

上面這段程式碼有幾個地方值得注意:
  • Populate 是個擴充方法,由 Microsoft.Framework.DependencyInjection.Autofac 組件提供。此擴充方法會把傳入的服務描述清單中的服務全部註冊至 Autofac 容器。
  • 注意 ConfigureServices 方法的回傳型別從原先的 void 改成了 IServiceProvider。這裡傳回的物件實際型別會是 AutofacServiceProvider。如此一來,ASP.NET vNext 框架接收到你回傳的 AutofacServiceProvider 物件之後,就會把它設定成預設的 DI 容器。 
試執行應用程式,結果應該和前一個版本相同。


參考資料