這是以 Microsoft.Extensions.DependencyInjection
為主要 DI 容器
並且透過 Attribute 來設定注入方式
在安裝的專案目錄下輸入指令
dotnet add package AttributeModel.Extensions.DependencyInjection
一般類別庫僅需要透過以 Lifetime
為名的屬性對類別裝飾即可
[Singleton]
public class SingletonService {}
[Scoped(ServiceType = typeof(IScoped))]
public class ScopedService : IScoped {}
[Transient(ServiceType = typeof(ITransient<>))]
public class TransientService<T> : ITransient<T> { }
並且於最末端的專案 例如: Web專案 於該專案中註冊 DI 容器的地方加上下面這段 此處以 AspNetCore Web 專案中的 Startup.ConfigureService 為例
public void ConfigureService(IServiceCollection services)
{
// 加上這行即可
services.AddAttributeModelRegister();
}
簡單說明一下其內部運作方式
- 運用 SourceGenerator 中的分析器分析 Attribute 並將其轉換成為相對應的容器註冊的程式碼
- 將產生的程式碼寫在一個由當下 AssemblyId 的 Namespace 下名為 ServiceRegistryAttribute 的類別中
- 將該類別繼承 Attribute 並且於 Assembly 級別上直接宣告該 Attribute
- 最後
AddAttributeModelRegister
就是在所有載入的 Assembly 中找出所有的 ServiceRegistryAttribute 並且將 IServiceCollection 帶入並完成註冊
- 一般 Attribute 類型的做法都必須透過於執行階段去掃描所有的類型,
而透過 SourceGenerator 則是在設計階段就完成所需要的註冊程式碼,
於執行階段僅搜尋到 Assembly Level 的 Attribute,
啟動時的效能可以好很多 - 由於 dotnet 內建的 DI 容器幾乎是沒有開放擴展的,
因此如果想要實現比較特殊的模式 (ex: 裝飾器模式), 會變得異常的困難或是複雜, 透過 設計階段的 Attribute 來實現註冊等同於額外提供了一個重新設計的註冊方式的空間,
因此可以大幅度的加強其可擴展性
(但我還沒實現這件事情 orz)
- 因為 Attribute 無法使用 非 const 的參數
因此在必須透過方法的實現註冊的案例中
那些方法該放在哪裡變得有點尷尬
不過由於 dotnet 的 DI 註冊方式已經盡可能地避免這件事情發生 以及目前也已經實現一種做法來提供這部分的需求
所有註冊容器所使用的各式自定義的方法
都離不開最初提供的那幾個註冊方式
例如:
services.Configure<MyOptions>(options => configuration.Bind(options));
實際上他背後程式碼請參考連結 source 透過原始碼發現他是用一個 class 裝載那段 lambda 並且註冊為 IConfigureOptions 以及 Singleton 因此透過下面做法也可以達到一樣的效果
[Singleton(ServiceType = typeof(IConfigureOptions<MyOptions>))]
public class MyOptionsConfigureOptions : IConfigureOptions<MyOptions>
{
private readonly IConfiguration _configuration;
public MyOptionsConfigureOptions(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure(MyOptions options)
{
_configuration.Bind(options);
}
}
在這個版本中引用了套件 Scrutor
透過此套件的裝飾器功能
現在可以透過 Decorator
屬性來註冊裝飾器
public interface IA
{
void Invoke();
}
[Decorator(serviceType = typeof(IA))]
public class A : IA
{
private readonly IA _a;
public A(IA a)
{
_a = a;
}
public void Invoke()
{
_a.Invoke()
}
}
由於 Scrutor
的裝飾器做法
必須在裝飾器註冊之前註冊被裝飾類別
這個困境在這裡將會被有效的解決
裝飾器將會在所有其他透過 Attribute 進行的註冊之後
才會開始註冊裝飾器
但如果需要保證註冊成功
亦需要於將 AddAttributeModelRegister
方法於最後註冊