Autofac Registering Components的方式繁多,本文將根據各個不同的註冊方式探討其中的優缺點和適用情境,以及附帶上實作範例。
導讀
前文介紹Autofac整體圖像時,描述了透過Register Type的方式,建立Component和Service之間的關聯。一般來說已能應付大部分的狀況。但除了Register Type的方式之外,還有多種註冊的方式,本文將額外探討下列共4種註冊Component的範例以及適用的情境。
- 以實體物件方式註冊
- 以Lambda Expression方式註冊
- 以Module方式註冊
- 以掃描Assembly方式註冊
以實體物件方式註冊
跟型別方式註冊非常類似,根據上文的例子來比較,如下圖所示,可以看到用物件方式註冊似乎比較麻煩,
var builder = new ContainerBuilder();
//型別方式註冊
builder.RegisterType<YourCar>().As<ICar>();
//物件方式註冊
var car = new YourCar()
builder.RegisterInstance(car).As<ICar>();
但還是有些情境,無法使用型別方式註冊,比如說,希望註冊的物件是來自某個工廠類別。
public class CarFactory
{
public static ICar CreateCar(string carType)
{
ICar car;
if (carType== "MyCar")
return new MyCar();
if (carType== "YourCar")
return new YourCar();
else
return null;
}
}
因為無法確切知道真正回傳的類別為何,所以就必須使用實體方式來註冊。
//實體類別來自工廠
var yourCar = CarFactory.CreateCar("YourCar");
builder.RegisterInstance(yourCar).As<ICar>();
以Lambda Expression方式註冊
使用Lambda Expression來註冊物件,請參考下列範例,範例的前置動作,簡單建立一個IDriver的介面,並且宣告HankDriver實作此介面,提供一個方法叫做SayName()。
public interface IDriver
{
void SayName();
}
public class HankDriver : IDriver
{
public void SayName()
{
Console.WriteLine("My name is Hank");
}
}
接著註冊HankDriver和IDriver
builder.RegisterType<HankDriver>().As<IDriver>();
最後修改MyCar的類別,簡單宣告建構子需要注入IDriver,並在MyCar內的Drive()方法呼叫IDriver的SayName()。
public class MyCar : ICar
{
private IDriver Driver;
public MyCar(IDriver driver)
{
this.Driver = driver;
}
public void Drive()
{
this.Driver.SayName();
Console.Write("Drive BMW");
}
}
到此,完成了範例的前置作業,現在需求是註冊MyCar成為ICar時,需額外注入Driver類別,此時便可使用Lambda expression,下面範例中的c是IComponentContext物件,只要在Register區塊有註冊的物件,在此都可以透過Resolve方式取得實體類別,來協助建立Component,並將它註冊成Service。
builder.Register(c=> new MyCar(c.Resolve<IDriver>())).As<ICar>();
以下為完整Autofac範例內容,以及Console輸出。
var builder = new ContainerBuilder();
builder.RegisterType<HankDriver>().As<IDriver>();
builder.Register(c=> new MyCar(c.Resolve<IDriver>())).As<ICar>();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var car = container.Resolve<ICar>();
var driver = new Driver(car);
driver.Dirve();
Console.ReadLine();
}
以Module方式註冊
為何會有Module的需求?
前面所描述的註冊方式,假如實作的邏輯需要抽換,則必須要修改程式碼並且重新編譯,但這會造成不好的使用者體驗。但如果將註冊的邏輯寫成JSON的Config檔案,的確可以省去需要重新編譯的困擾,但JSON檔的可維護性相對是比較差的,而且也違背封裝的意義,系統的邏輯寫在Config裡,怎麼看都怪怪的。為了解決這些問題,所以,還有一種很酷的註冊方式,就是使用Module來註冊。
甚麼是Module?
Module說穿了只是一個類別,主要功用是把Component和Service相關聯的註冊封裝起來。而且很方便的一點是Module之間是互通的,也就是說A Module註冊了A Service,B Module註冊了B Service,就代表系統如需注入A、B Service,可直接透過Autofac的Reslove來達到,即使兩個Service是隸屬於不同Module也沒關係。
那使用Module有那些好處?
- 相關聯的Component \ Service註冊,可以封裝在同一個Module的類別裡,大幅提升可讀性。
- Component可統一集中管理注入的Parameter,在維護上方便許多。
- Module是類別,須通過編譯檢查,所以是強型別。假如設定錯誤,編譯時就會發現,不會像JSON或XML爆掉了才會知道。
- 系統在Run Time時選用的Module可透過JSON或XML來設定,動態抽換,後面會用範例詳述。
實作一個範例來玩玩
基本環境安裝,需在Nuget下載下列三個套件
- Microsoft.Extensions.Configuration
- Microsoft.Extensions.Configuration.Json
- Autofac.Configuration
新增一個CarModule檔案,先來定義我們的Module,重點只有兩個,繼承Autofac Module,然後override Load方法。剩下的註冊方式跟前面一樣就不多說了。
public class CarModule:Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<HankDriver>().As<IDriver>();
builder.Register(c => new MyCar(c.Resolve<IDriver>())).As<ICar>();
}
}
接下來新增JSON檔案,type的輸入方式,第一個參數是命名空間+類別名稱,第二個參數是組件名稱,可請參考下面兩個示意圖。
{
"modules": [
{
"type": "DemoAutofac.Modules.CarModule, DemoAutofac"
}
]
}
最後,調整主程式,相關說明直接寫在註解當中。
var config = new ConfigurationBuilder();
//取得目前專案的路徑,原本預設是在bin的Debug資料夾
string basePath = Path.GetDirectoryName(Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
//設定成root路徑
config.SetBasePath(basePath);
//參數是與root路徑相對的位置
config.AddJsonFile("autofac.json");
//透過Config產生Module
var module = new ConfigurationModule(config.Build());
var builder = new ContainerBuilder();
//將Module註冊到Container內
builder.RegisterModule(module);
以上,就完成了Module的實作,當系統在執行時,抽換掉autofac.json檔案,便可即時改變系統的運作邏輯,雖然也不建議這樣做。另外,應該有發現,使用Module之後,相關聯的Component和Service都會封裝到一個具備意義的Module類別中,在後續維護系統時,也更加容易了解彼此的相依關係。
以掃描Assembly方式註冊
使用這種方式,狠一點根本不需要寫Component和Service間的註冊關係,可直接掃描dll後,自動用有實作該介面的類別來註冊。寫法很簡單,只需要載入指定的dll名稱,並宣告成AsImplementedInterfaces()即可。
//載入dll
var module = Assembly.Load("DemoAutofac");
//自動以實作介面的類別來註冊
builder.RegisterAssemblyTypes(module)
.AsImplementedInterfaces();
不過一般來說,會搭配Filter來使用,向本文的例子MyCar和HankDriver類別分別是Car和Driver結尾,所以,如果我只想註冊Car和Driver其他類別並不想註冊的話,可以添加Fiter來達到。所以採分層式開發的系統,可以直接用結尾名稱為Repository,自動對應註冊所有Repository的相關類別,其餘像Service等等都可以利用這種以限定結尾名稱的方式來自動對應。
var module = Assembly.Load("DemoAutofac");
builder.RegisterAssemblyTypes(module)
//此處搭配Filter使用
.Where( i=> i.Name.EndsWith("Car")|| i.Name.EndsWith("Driver"))
.AsImplementedInterfaces();
小結
本文介紹了一些常見的註冊Component和Serivce的方式,各有優缺點和適合的情境,就看開發者如何規劃自己的系統,找出最適合的模式。談完了Registering,接下來的主題會再對Resolving做深入的探討。