前言
Autofac
Autofac是一套高效的依賴注入框架。
Autofac官方網站:http://autofac.org/
Autofac在Github上的開源項目:https://github.com/autofac/Autofac
Autofac安裝:通過VS的Nuget可以很方便的獲取。
IoC/DI
關於IoC與DI的概念,網上有很多相關的博客,大家可以稍微了解一下,對比一下。
我個人的理解就是按照英文的中文翻譯來理解的:
IoC: Inversion of Control 控制反轉,將控制權進行反轉,將本由自身控制的對象初始化交由外部IoC容器進行初始化;
DI: Dependency Injection 依賴注入,將對象依賴的其他對象,通過注入的方式進行初始化。
簡單示例
我們先通過一個簡單的例子來看看Autofac的基本使用方式以及一些概念:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<Class_1>(); //將Class_1注冊到Autofac容器中 IContainer container = builder.Build(); Class_1 obj = container.Resolve<Class_1>(); //從autofac容器中獲取Class_1對象 Console.WriteLine(obj.Id); Console.Write("Press any key to continue..."); Console.ReadKey(); } }class Class_1 { public Guid Id { get; set; } public Class_1() { Id = Guid.NewGuid(); } }上面的代碼演示了最簡單注冊/獲取方式。我們先通過ContainerBuilder進行類型注冊,然后在后面可以通過IContainer進行獲取類型實例。這里我們也明白了一點,類型是需要先進行注冊,然后才能夠通過Autofac進行獲取(后面會談到一些特殊的配置,但是這也屬於另類的注冊方式)。
然后我們能知道在Autofac中,先通過ContainerBuilder進行類型注冊,然后通過ContainerBuilder的Build方法來獲取IContainer類型實例,后面則可以通過該IContainer實例來獲取注冊類型的實例對象。
Autofac類型注冊
類型/泛型注冊
在簡單實例中,我們已經看到了一種注冊方式,那就是泛型注冊:
builder.RegisterType<Class_1>();這種注冊方式很方便也很簡單,比較常用,但是有一個缺點就是,注冊的類型必須在當前項目或被當前項目引用,因為使用泛型,必須類型明確。針對這點,還有一種通過Type對象進行注冊的方式:
//字符串為類型完全名稱 builder.RegisterType(Type.GetType("AutofacBlog.Class_1"));使用這種方式進行類型注冊,被注冊的類型可以不是被直接引用,但是注意,類型所在的程序集必須被加載。
這種注冊方式,在有插件或類似需要動態加載程序集的情況下比較使用,通過掃描程序集,獲取一些滿足指定條件的類型,來進行注冊。
程序集批量注冊
類型注冊中提到了通過掃描程序集,來獲取部分類型進行注冊。Autofac對此提供了一個方便的方式,可以直接通過程序集來篩選類型注冊:
//獲取當前應用程序加載程序集(C/S應用中使用) var assembly = Assembly.GetExecutingAssembly(); var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(assembly); //注冊所有程序集類定義的非靜態類型上述代碼中,通過RegisterAssemblyTypes方法,將assembly中所有自定義的非靜態類型都注冊到Autofac中,后面可以使用IContainer對象獲取所有該程序集中自定義的類型對象。
這種方式達到了批量的效果,但是通常,我們並不需要把所有的類型都進行注冊,所以Autofac提供了幾種過濾方式:
builder.RegisterAssemblyTypes(assembly) .Where(type => type.Namespace.Equals("IocAutofac.Example")); //條件判斷這種方式相信大家都比較熟悉,Where+lambda的方式來進行條件判斷,lambda參數為Type類型,也就是程序集中的type。
builder.RegisterAssemblyTypes(assembly) .Except<Program>(); //排除Program類型這種方式用來排除指定類型的注冊,當排除的個例比較少時,會比較適用。Except還有另一種用法,但是用法比較復雜,在此不進行介紹。
builder.RegisterAssemblyTypes(assembly) .InNamespace("IocAutofac.Example"); //類型在IocAutofac.Example命名空間中被注冊的類型需要在指定命名空間中。
builder.RegisterAssemblyTypes(assembly) .InNamespaceOf<Program>(); //類型在Program所在的命名空間中*/這種方式與上面一種方式比較相似,也是用來判斷命名空間的,這種方式是根據類型來獲取命名空間。
通過這種方式,我們可以對程序集中的類型進行批量注冊,類型/泛型方式在被注冊類型較少的情況下還是不錯的,但當被注冊類型很多的時候,一個一個的手寫注冊會顯得很無力,這時候就是程序集批量注冊顯威的時候了。
Lambda注冊
上面講到的兩種方式都是通過類型進行直接注冊的,這種注冊方式,在獲取時,會直接通過構造函數new出對象(關於構造函數的優選選擇在后面的博文中將進行說明),不會做更多的操作。有時,我們希望能夠在獲取對象時能夠自動的做更多的事情時,我們可以通過Lambda注冊來解決:
builder.Register(cc => { var clas1 = new Class_1(); while (clas1.Id.ToString().Contains("a")) { clas1.Id = Guid.NewGuid(); } return clas1; });上述代碼,實際注冊了Class_1類型,因為最后返回的對象類型為Class_1。
Register方法接收了一個lambda表達式作為參數,在lambda表達式中,我們可以做很多事,包括一些屬性注入(后續說明)、方法注入(后續說明)、條件判斷等等。
我們在通過Autofac獲取Class_1對象時,實際會執行這樣的一個表達式。另外,lambda表達式的參數cc的類型是IComponentContext,這里我們可以簡單的當做IContainer進行使用。
實例注冊
var clas1 = new Class_1(); clas1.Id = Guid.Empty; builder.RegisterInstance(clas1);通過RegisterInstance進行實例注冊,進行實例注冊時,我們需要注意,實例注冊可以作為一種單例注冊的方式,也就是在后面通過Autofac獲取Class_1對象時,獲取到的是注冊時的那個對象。並且,如果一個在某處修改了該對象,其他地方再獲取時,獲取到的就是修改后的對象。
Module注冊
在日常開發中,可能不同開發會負責不同的模塊進行單獨開發。在開發過程中,不同模塊不同開發可能都有自己的類型需要注冊到autofac中,但是如果每個人在注冊時,都去修改一個指定地方的代碼,這在進行代碼合並時,是令人痛苦的。更好的方式是,每個開發不同的模塊都有自己指定的類型注冊區域,這樣,在代碼合並時,會減少很多代碼沖突。
對於這種方式,Autofac已經為我們提供了:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule<ModuleA>(); //這兩種注冊方式達到的效果都一樣 builder.RegisterModule(new ModuleB()); IContainer container = builder.Build(); Class_1 clas1 = container.Resolve<Class_1>(); Class_2 clas2 = container.Resolve<Class_2>(); Console.WriteLine(clas1.Id); Console.WriteLine(clas2.ToString()); Console.Write("Press any key to continue..."); Console.ReadKey(); } } class ModuleA : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<Class_1>(); } } class ModuleB : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<Class_2>(); } }class Class_2 { }上述代碼中,有兩個繼承自Module類的類:ModuleA、ModuleB,這兩個類型重寫了父類的Load方法,並在load方法中,分別注冊了Class_1與Class_2類型。然后在主程序中,通過RegisterModule對Module進行注冊。
通過這種方式,不同的開發就可以各自創建一個類繼承自Module,然后重寫Load方法,在Load方法進行自己的類型注冊,最后再進行Module的統一注冊。
Module注意說明
實際上,RegisterModule需要的參數,並不是繼承自Module類的,而是實現了IModule接口的類,而Module也是實現了IModule接口的。也就是我們也可以寫一個實現了IModule接口的類型,然后在RegisterModule時傳入。但是一般我們直接去繼承Module就好了,這種方式比較簡單方便,實現IModule的方式更為復雜,當然,功能也更多,在此就不進行介紹了。
程序集Module注冊
Module注冊,為多人開發提供了一種方便的注冊方式,但是我們也可以發現,這種方式,還是會需要手動注冊Module,如果Module過多,Module注冊代碼也會顯得多而雜,當然,可以通過人工管理來控制Module的量。但是Autofac還提供了一種更方便的方式,並且,對於類似Orchard的模塊開發(子模塊與主模塊無引用關系,通過程序集加載方式來加載子模塊)或是插件開發,我們沒辦法通過Registerodule來注冊無直接引用關系的Module。
對於上述的情況,Autofac提供了很好的方式來解決:
var builder = new ContainerBuilder(); var assembly = Assembly.GetExecutingAssembly(); builder.RegisterAssemblyModules(assembly);上述代碼會注冊assembly程序集中所有實現了IModule接口的類型(多層繼承也算),這樣,我們只需要將取出所有程序集,然后通過RegisterAssemblyModules進行一次性注冊,就可以自動注冊所有Module了。
RegisterAssemblyModule還可以指定一個泛型類型:
builder.RegisterAssemblyModules<ModuleA>(assembly);這樣注冊,是指定只注冊assembly程序集中繼承自ModuleA的Module。
尾述
本篇博文主要講述Autofac中比較常用也比較好用的注冊方式,並沒有把所有的注冊方式都講述出來。
個人非常推薦使用Module,每個項目擁有自己的一個Module,這樣的一個Module都有固定的未知,便於查找該項目中的注冊及依賴關系。
通過本篇博文,可以了解到幾種常用的注冊方式,以及最簡單的對象獲取方式,初學者可以簡單的試試。
本篇博文講述的內容可能與大家平時使用autofac的方式不一樣,平時使用時還會使用As,但是我個人認為這個不屬於單純注冊的內容,所以將這部分放到后面的博文中。