.Net: 善用 IServiceCollection Extension 和自製 Builder,讓服務註冊更有約束吧
在之前的依賴注入文章的「組合根請稍作分類」小節,我們介紹過使用 IServiceCollection 的擴充方法來對要註冊的服務進行分類和管理的做法:
public static class ServiceCollectionExtensions
{
/// <summary>
/// 註冊 Nice Service
/// </summary>
public static IServiceCollection AddNiceServices(this IServiceCollection services)
{
services.AddScoped<INiceService, NiceService>();
// ...一些相關的註冊
return services;
}
}
// Program.cs
builder.Services.AddNiceServices();
一直以來我都採用這個方法來簡單地拆分我的服務註冊,在大多數的場合已經足夠使用(尤其是只關注目前的專案時)。
但前幾天在看某個套件的實作時,開發的朋朋跟我分享了一些延伸的做法,感覺合理又常見,屬於有注意到就會記得的小技巧,決定馬上來記錄一篇筆記。
場景是這樣的:當我們在開發套件的時候,常常會有主要套件跟擴充用的延伸套件。
例如說,我可能會有一個主要的 NiceTool
套件,跟擴展前者的 NiceTool.Memory
套件
這兩個套件都會需要對 IServiceCollection 註冊一些服務,並且延伸套件 NiceTool.Memory
需要確認 NiceTool
的服務都有進行註冊才能正常運作。
如果我們想要先確保主要套件 NiceTool
的服務都註冊了,再處理 NiceTool.Memory
,同時又想保留擴充組合的彈性給以後的 NiceTool.SqlServer
,甚至我們可能想在兩者各自的註冊加點工、傳遞點資訊,可以怎麼辦呢?
這時候就可以考慮包一個自家的 Builder,用這個 Builder 來把這些套件給串起來。
以上面的例子來說,我可以製作一個 INiceToolBuilder
,並且把服務註冊時要用到的 IServiceCollection
包在裡面:
public interface INiceToolBuilder
{
IServiceCollection Services { get; }
}
public class NiceToolBuilder
{
public IServiceCollection Services { get; }
public NiceToolBuilder(IServiceCollection services)
{
Services = services;
}
}
接著,在作為註冊起點的主要套件 NiceTool
裡,我就可以提供一個產生 INiceToolBuilder
的擴充方法:
public static INiceToolBuilder AddNiceTools(this IServiceCollection services)
{
services.AddScoped<INiceService, NiceService>();
// ...一些相關的註冊
var builder = new NiceToolBuilder(services);
return builder;
}
接著,在延伸的 NiceTool.Memory
,我就可以接續著使用 INiceToolBuilder
來對 IServiceCollection
進行服務註冊:
public static INiceToolBuilder AddNiceMemoryTools(this INiceToolBuilder builder)
{
// 對 INiceToolBuilder 帶進來的 IServiceCollection 繼續進行服務註冊
builder.Services.AddScoped<INiceMemoryService, NiceMemoryService>();
// ...一些相關的註冊
return builder;
}
如此一來,我們就可以明示使用者在註冊的時候,先把主要套件提供的服務註冊方法呼叫完並取得 INiceToolBuilder
,再進行延伸套件的服務註冊。如果有什麼資訊需要帶著一起走,也可以直接包在裡面就好。
在 Program.cs
也可以用我們熟悉的串串樂舒暢地一路串完,並讓使用者自行組合:
builder.Services
.AddNiceServices()
.AddNiceMemoryTools();
是不是看起來乾淨舒服,又方便有約束力呢。
為什麼會說這是「有注意到就會記得的小技巧」呢?因為這個做法其實還蠻常見的,尤其是各種套件和工具。
例如大家應該都很熟悉的 AddHealthChecks()
,回傳的就是 IHealthChecksBuilder
,後續就能接著去呼叫另一顆套件的 AddSqlServer()
又或者是我們 IOptions 文章介紹過的 AddOptions()
,回傳的是 OptionsBuilder
;
隔壁的 AddLogging()
提供傳入 ILoggingBuilder
的委派作為參數等等……
如果只是一路 Add()
Use()
Add()
Use()
,可能就不會注意到這個設計上的小巧思。
像我個人以前比較少做套件,大多時候都在服務內處理,習慣 IServiceCollection 幹到底,就不曾留心在這部份過。這次有朋朋跟我分享了這個眉角,馬上跟之前註冊各種套件工具時的記憶連了起來。
延伸想了一下,同樣的概念也不是只有套件開發的時候會用到,那不如記錄下來,以後才方便抄(?)
如此如此,這般這般,又成功靠偷朋朋的小技巧水了一篇,功德圓滿,阿彌陀佛。
其他文章
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)