【.NET Core 3.0】小技巧 || 原生DI一对多注入
本文是一個技巧文章,內容很短,但是被提問的頻率很高,所以記錄下來,以待大家不時之需。
以下的代碼,是通過原生的依賴注入來講解的,其他的第三方框架,可以自己自定義擴展,效果是一樣的,那咱們先來回顧下依賴注入,都有哪幾種情況。
?一、依賴注入有哪幾種情況
關于依賴注入,其實我已經寫了很多的文章,也錄了很多的視頻了,這里就不過多的解析什么了,無論是原理,還是好處,甚至是使用場景等等,如果你還不是很了解,可以看看我的視頻。
https://www.bilibili.com/video/av58096866?p=5
https://www.bilibili.com/video/av73194514
上邊的這個是基礎和核心知識點,下邊的是我直播的時候,手寫的代碼,可以根據自己的需要去查看。
總體來說,我一直講的依賴注入的方式,都是面向抽象的 很常見的:一個類對應一個接口。那還有其他的注入情況么?當然還有很多,比如:
1、單獨的一個類注入;
2、一個類繼承了多個接口;
3、一個接口有多個實現類;
當然,除了上邊這三個,還有單獨類的 AOP 操作等等,一個類對應一個接口的情況,我們已經說了很多了,這里就不說了,一個類多個接口的,這個也不用說,其實就和一個類對應一個接口,是一個效果,那我們就先說說注入單獨的一個類和,一個接口對應多個實現,這兩種情況吧。
單獨注入一個類很簡單,大家都知道,依賴注入,其實就是實例化的過程,然后管理我們的對象的生命周期,降低耦合等等多個好處。
那我們既然是實例化的過程,簡單啊,放到容器,直接使用它!
/// <summary> /// 1、定義一個單獨類,不繼承任何 /// </summary> public class OneSeparateService { /// <summary> /// 寫一個方法,可以通過類型返回不同內容 /// </summary> /// <param name="type"></param> /// <returns></returns> public string SayHello(string type="") {if (type == "English") { return "Hello"; } else { ?????????????return?"你好"; } } }
//?2、注入容器services.AddScoped<OneSeparateService>();
/// <summary>/// 3、構造函數注入/// </summary>/// <param name="oneSeparateService"></param>public WeatherForecastController(OneSeparateService oneSeparateService){ _oneSeparateService = oneSeparateService;}
?//?4、調用 [HttpGet] public object Get() { // 依賴注入,就等于下邊的直接new一個實例 //OneSeparateService oneSeparateService = new OneSeparateService(); return oneSeparateService.SayHello("English");?}
我們只需要直接構造函數注入,即可使用,有種靜態方法的意味,是不是很簡單!當然很簡單啦,因為今天我們不是說這個的,說這個僅僅是一個開胃菜,體會一下注入的過程而已。
好啦,熱身完成,下面,我們就詳細的說說如何實現一個接口多個實現類吧。
?二、如何注入一對多
既然說到了一對多,那現在我們就來模式一下數據:
/// <summary>?///?1、定義一個接口 /// </summary> public interface IMoreImplService { string SayWelocome();?}??///?<summary> /// 2、分別定義兩個實現類 /// </summary> public class WelcomeChineseService : IMoreImplService { public string SayWelocome() { return "歡迎"; } }public class WelcomeEnglishService : IMoreImplService { public string SayWelocome() { return "Welcome"; }?}
然后我們準備好了,該注入了,你可能會說,簡單呀!直接注入然后調用不就好了:
services.AddScoped<IMoreImplService, WelcomeChineseService>();services.AddScoped<IMoreImplService,?WelcomeEnglishService>();然后直接調用
[HttpGet] public object Get() { return _moreImplService.SayWelocome();??}
這個時候,是不是有點兒懵,嗯?那我現在到底調用的是哪個實現類呀,我們運行看看效果就知道了:
可以看到是?Welcome?,正好和我們的注入順序是一致的,就是說,無論注入多少個,最終是最后一個生效,就好像 = new () 了好幾次,把之前的實例給覆蓋了一樣,你應該懂了吧,不信的話,可以把注入的順序換換,就知道啦。
請記住,剛剛我用的是?好像?字眼,真的是被覆蓋掉了么,我們來看看就知道了,既然是注入了多個,我們就把多個實例都拿出來:
[HttpGet] public object Get() { var result = ""; // 調用多次輸出 foreach (var item in _moreImplServices) { result += item.SayWelocome() + "\n"; }
return result;?}
詳細這個時候,你應該猜得出來答案了吧:
把兩個實例都打印了出來,這就說明一個問題,我們在容器里,并不是在注入的時候,后來的把前邊的給覆蓋掉了,而是 本來容器里就有多個接口實例映射關系 ,只是我們在 controller 控制器里取的時候,只獲取了最后一個而已!
那明白了這個問題,我們就很開心了,容器里還是都有的,我們還是可以按照我們的需要,取出想要的某一個,那我們就猜想了,如何區分呢,在文章開頭,我們定義方法的時候,就是想著用一個 type ,那這里我們能不能用一個別名來做區分呢,欸,重頭戲來了:
/// <summary> /// 根據別名,獲取接口實例 /// </summary> /// <typeparam name="TService"></typeparam> /// <param name="id"></param> /// <returns></returns> public TService GetService<TService>(string id) where TService : class { // 獲取接口的類型 var serviceType = typeof(TService); // out 方法,先獲取某一個接口下的,<別名,實例>字典 if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict)) { // 根據別名,獲取接口的實例對象 if (implDict.TryGetValue(id, out object service)) { // 強類型轉換 return service as TService; } } return null; }
/// <summary> /// 將實例和別名 匹配存儲 /// </summary> /// <typeparam name="TService"></typeparam> /// <param name="service"></param> /// <param name="id"></param> public void AddService<TService>(TService service, string id) where TService : class { var serviceType = typeof(TService); // 對象實例判空 if (service != null) { // 如果不存在,則填充 if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict)) { implDict[id] = service; } else { implDict = new Dictionary<string, object>(); implDict[id] = service; serviceDict[serviceType] = implDict; } } }?}
上邊的代碼相信應該能大致看的明白,看不明白也沒關系,主要一句話概括,就是使用了一個服務,維護一個字典,利用泛型,先把對象實例和別名,分配存儲到字典,然后再根據別名或者指定接口的對象實例,
就是把接口下的實現類,都 new 出來實例,然后匹配上別名,說白了,就是我們的 type,然后再輸出出來。
那下一步,我們就需要先把這個單例服務給注入進去:
SingletonFactory singletonFactory = new SingletonFactory(); singletonFactory.AddService<IMoreImplService>(new WelcomeChineseService(), "Chinese"); singletonFactory.AddService<IMoreImplService>(new WelcomeEnglishService(), "English");?services.AddSingleton(singletonFactory);
這個應該都能看的懂,唯一的小問題,可能會問,為啥要把我們的 singletonFactory 給 Singleton 注入?
因為我這是一個對象實例,只能是單例了,而且里邊的多個服務因為已經new實例過了,所以沒辦法控制生命周期。
最后我們就來調用看看:
// 各自定義需要的多個字段private readonly IMoreImplService moreImplServiceChinese;private readonly IMoreImplService moreImplServiceEnglish;public WeatherForecastController(SingletonFactory singletonFactory){ this.singletonFactory = singletonFactory;
// 根據別名獲取服務 moreImplServiceChinese = singletonFactory.GetService<IMoreImplService>("Chinese"); moreImplServiceEnglish = singletonFactory.GetService<IMoreImplService>("English");}
[HttpGet("/welcome")]public object GetWelcome(){ return moreImplServiceChinese.SayWelocome();}
結果我們不用看了,已經成功了,最后我們再來回顧一下這種寫法的步驟:
1、定義一個單例服務類,將我們的多個對象new出來實例,和別名對應存儲起來;
2、將單例類實例化后,注入服務容器;
3、控制器獲取單例類,并根據別名獲取相對應的服務;
到了這里,我們就已經完成了,是不是到了這里,感覺是已經完成了,但是又感覺哪里不是很舒服,比如這樣注入的實例都是單例的,那這樣不是很合適呀?
?三、簡單工廠模式注入【推薦】
如何才能適應不同的生命周期呢,我這里提供第二個方法:
// 先把多個實現類服務注入進去 services.AddScoped<WelcomeChineseService>(); services.AddScoped<WelcomeEnglishService>();// 然后通過簡單工廠模式,針對不同的 key 來獲取指定的對象實例 services.AddScoped(factory => { Func<string, IMoreImplService> accesor = key => { if (key.Equals("Chinese")) {?????????????// 因為這里是從容器獲取服務實例的,所以我們可以控制生命周期 return factory.GetService<WelcomeChineseService>(); } else if (key.Equals("English")) { return factory.GetService<WelcomeEnglishService>(); } else { throw new ArgumentException($"Not Support key : {key}"); } }; return accesor;?});
大家可以看一下,我們用的是 Scope 方式注入的,三種生命周期都可以,接下看就是調用了:
// 將我們的規則 Func 構造函數注入 private readonly Func<string, IMoreImplService> _serviceAccessor;public WeatherForecastController(Func<string, IMoreImplService> serviceAccessor) { // 獲取特定接口的服務訪問器,然后根據別名獲取 _serviceAccessor = serviceAccessor;?????//?這里的別名,你可以配置到 appsetting.json?文件里,動態的修改獲取對象實例???? // 然后再在接口中配置一個字段 string ImplementKeyName { get; } moreImplServiceChinese = _serviceAccessor("Chinese"); moreImplServiceEnglish = _serviceAccessor("English"); }
[HttpGet("/welcome")] public object GetWelcome() { return moreImplServiceChinese.SayWelocome() + "\n" + moreImplServiceEnglish.SayWelocome();?}
為了演示效果,我把Service服務的構造函數,增加一個動態時間,來判斷是否滿足Scope需求,那現在我們就來看看效果吧:
好啦,最后我們來總結一下這個方法的優點:
1、可以實現一個接口對應多個實現類的注入和獲取;
2、實例別名可以配置到 appsettings.json 里,動態獲取指定服務;
3、可以指定生命周期;
4、更直觀,更簡單;
雖然這種簡單工廠的寫法,還是不夠優雅,但是畢竟這種一個接口多個實現類的方法本身就不是很優雅,好啦,今天就分享到這里吧。
總結
以上是生活随笔為你收集整理的【.NET Core 3.0】小技巧 || 原生DI一对多注入的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 从AppDomain迁移到Assembl
 - 下一篇: ASP.NET Core 3.0 gRP