Autofac Resolving Service我認為會遇到的第一個困難就是參數的傳遞,本文主要是介紹多種參數傳遞的方式,以及如何透過工廠來生成物件,並附帶上簡單的實作範例幫助理解。
前言
我們前面分享了如何註冊Component到Autofac的Container中,接下來本篇的重點是如何透過Rsolve方法取得實際的物件。
在實際應用上,大部分物件都會提供需要參數的建構子來做初始化的動作。所以,如果要使用Autofac來生成物件,便需要告訴Autofac該如何傳遞參數。而傳遞的參數可以在程式碼執行階段時動態賦予,但在某些情況下,假設已經知道該參數的內容,而非動態產生的,舉例來說,像是寫在Config裡的連線字串,那就可在註冊Component時就定義好要傳遞的參數,這樣一來,可讓Resolve方法的動作更為單純。
傳遞參數篇
Autofac支援的參數傳遞的方式,大致上分為三種
- NamedParameter => 利用名稱來對應
- TypedParameter => 利用型別來對應
- ResolvedParameter => 透過Container相關的資訊傳遞參數 (較難理解,後面再以範例說明)
NamedParameter&TypedParameter 範例
前面兩種參數傳遞的方式,看敘述就能理解,但還是寫個小範例踏實點。第一個範例超簡單,直接看程式碼就能懂了,沿用前面的例子,簡單新增一個Truck類別,特別的地方只有在建構子處要求傳遞一個字串參數。
//建構子需要吃一個字串參數
public Truck(string name)
{
this.Name = name;
}
private string Name;
public void Drive()
{
Console.Write(string.Format("{0} Drive Truck",this.Name));
}
接著把它註冊到Container裡面,如對此段不熟悉可參考淺談Autofac一文。
var builder = new ContainerBuilder();
builder.RegisterType<Truck>().As<ICar>();
var container = builder.Build();
最後進入正題,透過Resolve方法來取的Truck的實體,利用NamedParameter方式來處理,只需先實體化一個NamedParameter物件,第一個參數對應到Truck建構子參數的名稱"name",第二個參數是希望傳遞的值,這個例子是"Hank"。
using (var scope = container.BeginLifetimeScope())
{
//new 一個NamedParameter物件
var truck = scope.Resolve<ICar>(new NamedParameter("name", "Hank"));
truck.Drive();
Console.ReadLine();
}
執行畫面如下。
再來把參數傳遞的方式從Named對應改寫成Typed對應,只需將上述例子的NamedParameter改為TypedParameter即可,第一個參數是Truck建構子參數對應的類別,第二個參數一樣是希望傳遞的值。修改完成後,再執行一次會發現結果相同。
var truck = scope.Resolve<ICar>(new TypedParameter(typeof(string), "Hank"));
使用Type的方式註冊可避免建構子的參數名稱改變而受到影響,但是假如參數有多個是相同型別,就會比較麻煩。反觀用Name的方式就蠻直覺,即使建構子內有多個相同型別的參數也沒問題,因為參數名稱是絕對不會重複的。
ResolvedParameter 範例
ResolvedParameter是很有彈性的一個給予參數的方式,但也稍難理解。下圖簡單描述一下Autofac的運作流程。起手式產生一個Builder,透過Builder來註冊Component,然後呼叫Builder的Build方法可得到一個Container,最後,就可以透過Container的Resolve方法生成物件。
有了上面的概念後,再來看ResolvedParameter的方法,它需要兩個參數,兩個參數的型別都很長一串,難以口述解釋。直接由下圖來理解,先來看第一個參數,這是一個委派,此委派需要兩個參數,ParameterInfo封裝了目標物件建構子參數的資訊,IComponentContext提供可透過介面操作Container的功能,最後回傳一個布林值,表示是否Match;第二個參數也是委派,同樣需要兩個參數,因跟前面相同就不贅述,差別在它回傳的是一個object,也就是要注入目標物件建構子參數的值。
再來看一個範例幫助理解,小小修改原本的Truck的例子,改為接受一個IDriver的參數。
public Truck(IDriver driver)
{
this.Driver = driver;
}
public void Drive()
{
this.Driver.SayName();//實作內容是Console.Write("My name is Hank");
Console.Write("Drive Truck ");
}
在註冊的部分,註冊HankDriver和Truck。
var builder = new ContainerBuilder();
builder.RegisterType<HankDriver>().As<IDriver>();
builder.RegisterType<Truck>().As<ICar>();
var container = builder.Build();
接著Resolve<ICar>時,採用ResolvedParameter的方式給予參數,ResolvedParameter的第一個參數是先下條件限制來找到要注入的參數,ResolvedParameter的第二個參數則是產生要注入參數的值,此範例是透過contrainer的Resolve方法產生要注入的值。
using (var scope = container.BeginLifetimeScope())
{
var truck = scope.Resolve<ICar>(new ResolvedParameter(
//pi 就是truck的建構子參數資訊,在這裡判斷建構子的參數名稱必須是driver 且型別為IDriver 才注入
(pi, ctx) => pi.ParameterType == typeof(IDriver) && pi.Name == "driver",
//前面參數條件符合,就注入該參數的值,這裡可透過ctx取得Container物件然後由Resolve方法生成物件
(pi, ctx) => ctx.Resolve<IDriver>()
));
truck.Drive();
Console.ReadLine();
}
最後執行一下,可看到結果如下。以上就是透過參數傳遞的方式來請Resolve生成物件。
同場加映(工廠方法)
除此之外,我們也可透過工廠的方式來生成物件,可以讓注入物件的方式更加彈性。我們就根據前面的例子來改寫,改由工廠的方式來生成物件,首先,在Truck類別內宣告一個delegate,回傳的類別是Truck,並且簽名對應到Truck的建構子。
//新增此行
public delegate Truck CarFactory(IDriver driver);
private IDriver Driver;
public Truck(IDriver driver)
{
this.Driver = driver;
}
接著把Truck類別註冊到Container內,因為建構子有用到IDriver,所以順手也註冊一下HankDriver。
var builder = new ContainerBuilder();
builder.RegisterType<Truck>();
builder.RegisterType<HankDriver>().As<IDriver>();
var container = builder.Build();
最後,透過resolve方法可取得carFactory,然後透過carFactory.Invoke方法判斷簽名,呼叫對應的建構子生成物件。
//取得Factory
var carFacotory = scope.Resolve<Truck.CarFactory>();
//透過Invoke方法呼叫建構子生成物件
var driver = scope.Resolve<IDriver>();
//也可寫成var truck = carFacotory(driver);
var truck = carFacotory.Invoke(driver);
truck.Drive();
Console.ReadLine();
這裡,也許大家會發現,用工廠的方式,好像程式碼變多了,比如說我要先透過var driver = R<IDriver>() 取得要注入的driver,然後再將driver當參數餵給工廠,就顯得很麻煩。其實,Autofac是很聰明的,它會自動幫你注入你有註冊的物件,所以來看一下如何修改這段,首先,把delegate的參數拿掉。
//public delegate Truck CarFactory(IDriver driver);
public delegate Truck CarFactory();
private IDriver Driver;
public Truck(IDriver driver)
{
this.Driver = driver;
}
接下來生成物件的方式就顯得簡單多了,只有一個參數也許感覺差別不大,但假如參數有10個以上時,就會發現Autofac真的蠻聰明的。
//取得Factory
var carFacotory = scope.Resolve<Truck.CarFactory>();
//透過Invoke方法呼叫建構子生成物件
//var driver = scope.Resolve<IDriver>();
//var truck = carFacotory.Invoke(driver);
var truck = carFacotory.Invoke();
truck.Drive();
Console.ReadLine();
輸出結果如下圖所示。
小結
本文介紹了一些常見的參數傳遞方式來透過Resolve方法生成物件,同時也介紹了工廠模式,接下來的主題會探討透過Autofac生成物件後,如何做生命週期管理的相關內容。