注意:本文為原創文章,任何形式的轉載、引用(包括但不限於以上形式)等,須先徵得作者同意,否則一切後果自負。
簡介
Autofac 是一個令人著迷的.NET IoC 容器。
它管理類之間的依賴關係。當應用程式的規模和複雜性隨著時間不斷增長時,也能易於更改。這是通過將常規 .NET 類視為元件來實現的。
入門
將 Autofac 整合到我們的應用程式的基本模式是:
- 時刻牢記用控制反轉(IOC)來構建我們的應用程式。
- 新增Autofac引用。
- 在應用程式啟動時…
- 建立一個ContainerBuilder。
- 註冊元件。
- 構建容器並將其儲存以備後用。
- 在應用程式執行期間…
- 從容器建立一個生命週期範圍。
- 使用生命週期範圍來解析(resolve)元件的例項。
構建應用程式
控制反轉背後的思想是:與其將應用程式中的類捆綁在一起,讓類“新建”它們的依賴關係,不如在類構造期間把依賴項傳遞進來,如果每次傳遞的依賴項不相同,則可以隨時切換依賴關係並呼叫相應的依賴項的實現。
對於我們的示例應用程式,我們將定義一個將當前時間寫出的類。但是,我們不希望將它繫結到Console,因為我們希望能夠稍後測試該類,或者在控制檯不可用的地方使用它。
我們將盡可能地把寫出時間的機制抽象化,因為如果我們以後想把它變成一個寫出明天日期的程式版本,那麼就可以快速實現該功能。
程式碼如下:
public interface IOutput
{
void Write(string content);
}
public class Output : IOutput
{
public void Write(string content)
{
Console.WriteLine(content);
}
}
public interface ITodayWriter
{
void WriteDate();
}
public class TodayWriter : ITodayWriter
{
private IOutput _output;
public TodayWriter(IOutput output)
{
_output = output;
}
public void WriteDate()
{
_output.Write(DateTime.Now.ToString());
}
}
現在我們有了一個合理的結構化的依賴集,下面我們就把Autofac加入進來!
新增 Autofac 引用
第一步是將Autofac引用新增到我們的專案中。
對於我們的示例,僅使用核心Autofac包就夠了,因為該包包含了我們所要使用的全部核心功能。
引入Autofac最簡單的方法是通過NuGet。
應用啟動
在應用程式啟動時,我們需要建立一個ContainerBuilder並在其中註冊我們的元件。
元件可以是一個表示式、.NET型別或其他程式碼(該程式碼公開一個或多個服務,並可以接受其他依賴項)。
簡單來說,考慮一個實現介面的 .NET 型別,像這樣:
public class SomeType : IService
{
}
我們可以用以下兩種方法來解決這個問題:
- 作為型別本身,SomeType
- 作為介面,IService
在這種情況下,元件是SomeType,它公開的服務是SomeType和IService(這一點你可能不太明白,不過沒關係,後面我們將會講解)。
在Autofac中,您可以使用以下內容進行註冊ContainerBuilder:
// 建立一個構造器
var builder = new ContainerBuilder();
// 註冊元件,並公開它的服務
builder.RegisterType<SomeType>().As<IService>();
// 如果你想把元件自身也註冊成服務,那麼可以使用AsSelf()
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<Output>().As<IOutput>();
builder.RegisterType<TodayWriter>().As<ITodayWriter>();
Container = builder.Build();
//該方法我們後面實現它
WriteDate();
}
}
}
現在我們有了一個容器,其中所有元件都已註冊,並且它們公開了適當的服務。讓我們來使用它吧。
應用程式執行
在應用程式執行期間,我們需要使用我們註冊的元件。我們可以通過生命週期範圍解析它們來做到這一點。
容器本身就是一個生命週期範圍,從技術上講,我們可以直接從容器中解決問題。但是,我非常不建議直接從容器解析。
當我們解析一個元件時,根據我們定義的例項範圍,會建立該物件的一個新例項。(解析一個元件大致相當於呼叫“new”來例項化一個類。這真的真的過於簡單化了,但從類比的角度來看,這個比較很恰當。)一些元件可能需要被釋放(就像它們實現了IDisposable一樣)——Autofac可以在生命週期範圍被釋放時自動為我們處理那些元件的釋放。
但是,容器在應用程式的整個生命週期內都存在。如果我們直接從容器中解決很多東西,等到最終,我們可能會有很多東西等待釋放。這非常的不好(如果我們這樣做,可能會發生“記憶體洩漏”的事故)。
相反,如果我們從容器建立一個子生命週期範圍並從中解析。當我們完成元件解析後,Autofac會幫我們自動釋放子作用域併為清理所有內容。
如果我們使用Autofac 整合庫時,這個子作用域的建立主要是自動為我們完成的,因此我們不必考慮它。
對於我們的示例應用程式,現在我們將實現“WriteDate”方法,它從一個子生命週期範圍獲取寫出結果,並在完成時自動處理該生命週期。
namespace DemoApp
{
public class Program
{
private static IContainer Container { get; set; }
static void Main(string[] args)
{
// 該內容在上面已經實現,不再重複
}
public static void WriteDate()
{
// 建立一個子生命週期範圍,解析ITodayWriter服務,呼叫方法,呼叫結束後被子生命週期範圍釋放掉
using (var scope = Container.BeginLifetimeScope())
{
var writer = scope.Resolve<ITodayWriter>();
writer.WriteDate();
}
}
}
}
現在當我們執行程式時......
- 該WriteDate方法建立了一個生命週期範圍,從中可以解析依賴項。這樣做是為了避免任何記憶體洩漏 - 如果ITodayWriter或其依賴項是一次性的,它們將在範圍被釋放時自動釋放。
- 該WriteDate方法從生命週期範圍內手動解析ITodayWriter。(這裡是“服務地點”。)在內部……
- Autofac 看到ITodayWriter對映到TodayWriter,所以開始建立一個TodayWriter.
- Autofac 認為在其建構函式中TodayWriter需要一個IOutput。(這是“建構函式注入”。)
- Autofac 看到IOutput對映到Output,所以建立一個新Output例項。
- Autofac 使用新Output例項來完成構建TodayWriter.
- Autofac 返回完全構造的TodayWriter供WriteDate消費。
- 對writer.WriteDate()的呼叫會轉到全新的TodayWriter.WriteDate(),因為這是已解決的問題。
- Autofac生命週期範圍已釋放。從該生命週期範圍解析的任何一次性項也將被處理。
之後,如果我們想讓我們的應用程式編寫一個不同的日期,我們可以實現一個不同的ITodayWriter,然後在應用程式啟動時更改註冊。我們不需要更改任何其他類。這是不是非常的棒,這就是控制反轉!