內容主要翻譯自官方文檔,原文請看:http://autofac.readthedocs.org/en/latest/getting-started/index.html#application-startup
將Autofac集成進你的應用的基本模式:
- 在腦海中構造基於控制反轉(IoC)的應用程序架構
- 添加Autofac引用.
- 在應用啟動配置流程...
- 創建一個 ContainerBuilder.
- 注冊組件(components).
- build定義的ContainerBuilder生成Autofac容器,並存儲它以供后續使用.
- 在程序運行時...
- 從Autofac容器(container)創建生命周期域(lifetime scope).
- 使用生命周期域來解析出組件實例.
這篇指導將會使用一個控制台程序一步一步演示如何使用Autofac. 一旦你有了基礎的了解,可以到以下wiki站點查看更高級的使用方法和技巧: integration information for WCF, ASP.NET, and other application types.
構建應用架構
基於IoC的想法應該是對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它,而不是把所有類綁在一起,每次都要通過依賴new來傳遞實例.如果你想了解更多請看:Martin Fowler has an excellent article explaining dependency injection/inversion of control.
對於我們的示例應用程序,我們將定義一個輸出當前日期的類。但是,我們不希望它和控制台綁在一起,因為我們希望以后能夠測試它或用在一個沒有控制台程序的地方。
我們還會去盡量抽象輸出日期的機制,以便於我們后續重復使用。
我們會做這樣的事情:
using System; namespace DemoApp { // This interface helps decouple the concept of // "writing output" from the Console class. We // don't really "care" how the Write operation // happens, just that we can write. public interface IOutput { void Write(string content); } // This implementation of the IOutput interface // is actually how we write to the Console. Technically // we could also implement IOutput to write to Debug // or Trace... or anywhere else. public class ConsoleOutput : IOutput { public void Write(string content) { Console.WriteLine(content); } } // This interface decouples the notion of writing // a date from the actual mechanism that performs // the writing. Like with IOutput, the process // is abstracted behind an interface. public interface IDateWriter { void WriteDate(); } // This TodayWriter is where it all comes together. // Notice it takes a constructor parameter of type // IOutput - that lets the writer write to anywhere // based on the implementation. Further, it implements // WriteDate such that today's date is written out; // you could have one that writes in a different format // or a different date. public class TodayWriter : IDateWriter { private IOutput _output; public TodayWriter(IOutput output) { this._output = output; } public void WriteDate() { this._output.Write(DateTime.Today.ToShortDateString()); } } }
添加Autofac引用
首先添加Autofac引用到你的項目,這個例子中我們僅添加Autofac Core引用,其他類型應用程序可能會用到不同的Autofac.Integration類庫。
通過NuGet可以很容易為項目引入引用,如圖:
應用啟動(Application Startup)
在🈺️啟動時,你需要創建一個ContainerBuilder並使用它注冊各種組件. 組件可以是表達式、.NET類型或者一些用於暴露內部服務的代碼。
簡單的來說,有個實現了接口的類型,如下:
public class SomeType : IService { }
你可以通過兩種方式定位到它:通過類型SomeType,或者通過接口IService.
在這個例子中,組件是SomeType,同時暴露的服務是指SomeType類型和IService接口。
在Autofac中,你應該通過ContainerBuilder來注冊它們。
// Create your builder. var builder = new ContainerBuilder(); // Usually you're only interested in exposing the type // via its interface: builder.RegisterType<SomeType>().As<IService>(); // However, if you want BOTH services (not as common) // you can say so: builder.RegisterType<SomeType>().AsSelf().As<IService>();
對於我們的示例應用程序,我們需要注冊我們所有的組件(類),並暴露他們的服務(接口)。
我們還需要存儲容器,這樣它之后可用於解析出各種類型實例。
using System; using Autofac; namespace DemoApp { public class Program { private static IContainer Container { get; set; } static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<ConsoleOutput>().As<IOutput>(); builder.RegisterType<TodayWriter>().As<IDateWriter>(); Container = builder.Build(); // The WriteDate method is where we'll make use // of our dependency injection. We'll define that // in a bit. WriteDate(); } } }
這樣我們就擁有了一個注冊了所有組件的容器,這些組件同時暴露了它們的服務。現在讓我們來使用這個容器。
應用運行時
在應用運行期間,你需要從容器生命周期域中解析出組件實例來使用它們。容器自身就是一個生命周期域(lifetime scope),技術上來講,你可以直接從容器中解析出需要的組件。
但是我們不推薦直接操作容器,這會導致內存泄漏。
當我們解析出一個組件時,依賴於我們定義的lifetime scope,一個新的對象實例會被創建。其中一些組件可能需要被釋放,例如實現IDispose接口的對象。當生命周期域被釋放時,Autofac可以自動處理組件資源的釋放。
但是容器存在於整個應用程序生命周期。如果你直接從容器中解析出大量組件,可能會導致很多組件一直等待被釋放。這樣做法並不好,通常會導致內存泄露。
替代方案是,每次我們從容器來創建子生命周期域,。當你從子域完成解析組件后,子域將會被釋放,同時所有資源都會被清理掉。
對於我們的示例程序來說,我們將會從子域中解析出IDateWriter實例,並運行WriterDate()方法。同時在我們完成后,子域將會被釋放。
namespace DemoApp { public class Program { private static IContainer Container { get; set; } static void Main(string[] args) { // ...the stuff you saw earlier... } public static void WriteDate() { // Create the scope, resolve your IDateWriter, // use it, then dispose of the scope. using (var scope = Container.BeginLifetimeScope()) { var writer = scope.Resolve<IDateWriter>(); writer.WriteDate(); } } } }
現在當你運行你的程序時...
- WriteDate()方法從Autofac中請求IDateWriter實例.
- Autofac發現IDateWriter可以映射到TodayWriter類型,所以創建了一個TodayWriter實例.
- Autofac發現TodayWriter在它的構造函數中需要一個IOutput實例.
- Autofac發現IOutput 可以映射到ConsoleOutput,所以創建了一個ConsoleOutput新實例.
- Autofac使用ConsoleOutput實例完成構造一個TodayWriter實例.
- Autofac返回了一個完全構造好的TodayWriter實例來用於“WriteDate” 方法.
之后,如果你的程序想要實現輸出不同日期, 你可以構造不同實現IDateWriter接口的類型,同時在應用啟動時注冊. 你不必改變其他任何的類. 這就是控制反轉!
注意:正常來說service location很大程度上被認為是反模式。也就是說,到處手動創建域(scope)和大量使用容器不一定是最好的方案。使用Autofac你通常不必做我們在示例應用中的做的事情。組件會從一個應用的中心或者頂層來解析,手寫的解決方案很罕見的。當然,如何設計應用程序是由你自己決定的。