最近有個需求就是一個抽象倉儲層接口方法需要SqlServer以及Oracle兩種實現方式,為了靈活我在依賴注入的時候把這兩種實現都給注入進了依賴注入容器中,但是在服務調用的時候總是獲取到最后注入的那個方法的實現,這時候就在想能不能實現動態的選擇使用哪種實現呢?如果可以的話那么我只需要在配置文件中進行相應的配置即可獲取到正確的實現方法的調用,這樣的話豈不快哉!今天我們就來一起探討下實現這種需求的幾種實現方式吧。
作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/10236163.html
代碼演示
在開始實現的方式之前,我們先模擬下代碼。由於真實系統的結構比較復雜,所以這里我就單獨建一個類似的項目結構代碼。項目如下圖所示:

接下來我來詳細說下上面的結果作用及代碼。
-
MultiImpDemo.I 這個項目是接口項目,里面有一個簡單的接口定義
ISayHello,代碼如下:public interface ISayHello { string Talk(); }很簡單,就一個模擬講話的方法。
-
MultiImpDemo.A 這個類庫項目是接口的一種實現方式,里面有一個
SayHello類用來實現ISayHello接口,代碼如下:/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述: *│ 作 者:yilezhu *│ 版 本:1.0 *│ 創建時間:2019/1/7 17:41:33 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 命名空間: MultiImpDemo.A *│ 類 名: SayHello *└──────────────────────────────────────────────────────────────┘ */ using MultiImpDemo.I; using System; using System.Collections.Generic; using System.Text; namespace MultiImpDemo.A { public class SayHello : ISayHello { public string Talk() { return "Talk from A.SayHello"; } } } -
MultiImpDemo.B 這個類庫項目是接口的另一種實現方式,里面也有一個
SayHello類用來實現ISayHello接口,代碼如下:/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述: *│ 作 者:yilezhu *│ 版 本:1.0 *│ 創建時間:2019/1/7 17:41:45 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 命名空間: MultiImpDemo.B *│ 類 名: SayHello *└──────────────────────────────────────────────────────────────┘ */ using MultiImpDemo.I; using System; using System.Collections.Generic; using System.Text; namespace MultiImpDemo.B { public class SayHello:ISayHello { public string Talk() { return "Talk from B.SayHello"; } } } -
MultiImpDemo.Show 這個就是用來顯示我們模擬效果的API項目,首選我們在
ConfigureServices中加入如下的代碼來進行上述兩種實現方式的注入:services.AddTransient<ISayHello, MultiImpDemo.A.SayHello>(); services.AddTransient<ISayHello, MultiImpDemo.B.SayHello>(); -
在api實現里面獲取服務並進行模擬調用:
private readonly ISayHello sayHello; public ValuesController(ISayHello sayHello) { this.sayHello = sayHello; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; }代碼很簡單對不對?你應該看的懂吧,這時候我們運行起來項目,然后訪問API'api/values'這個接口,結果總是顯示如下的結果:

兩種需求對應兩種實現
這里有兩種業務需求!第一種業務中只需要對其中一種實現方式進行調用,如:業務需要SqlServer數據庫的實現就行了。第二種是業務中對這兩種實現方式都有用到,如:業務急需要用到Oracle的數據庫實現同時也有用到SqlServer的數據庫實現,需要同時往這兩個數據庫中插入相同的數據。下面分別對這兩種需求進行解決。
業務中對這兩種實現方式都有用到
針對這種情況有如下兩種實現方式:
-
第二種實現方式
其實,在ASP.NET Core中,當你對一個接口注冊了多個實現的時候,構造函數是可以注入一個該接口集合的,這個集合里是所有注冊過的實現。
下面我們先改造下
ConfigureServices,分別注入下這兩種實現services.AddTransient<ISayHello, A.SayHello>(); services.AddTransient<ISayHello,B.SayHello>();接着繼續改造下注入的方式,這里我們直接注入
IEnumerable<ISayHello>如下代碼所示:private readonly ISayHello sayHelloA; private readonly ISayHello sayHelloB; public ValuesController(IEnumerable<ISayHello> sayHellos) { sayHelloA = sayHellos.FirstOrDefault(h => h.GetType().Namespace == "MultiImpDemo.A"); sayHelloB = sayHellos.FirstOrDefault(h => h.GetType().Namespace == "MultiImpDemo.B"); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHelloA.Talk() , sayHelloB.Talk()}; }然后運行起來看下效果吧

-
利用
AddTransient的擴展方法public static IServiceCollection AddTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;然后根據我們的配置的實現來進行服務實現的獲取。下面就讓我們利用代碼來實現一番吧:services.AddTransient<A.SayHello>(); services.AddTransient<B.SayHello>(); services.AddTransient(implementationFactory => { Func<string, ISayHello> accesor = key => { if (key.Equals("MultiImpDemo.A")) { return implementationFactory.GetService<A.SayHello>(); } else if (key.Equals("MultiImpDemo.B")) { return implementationFactory.GetService<B.SayHello>(); } else { throw new ArgumentException($"Not Support key : {key}"); } }; return accesor; });當然了,既然用到了我們配置文件中的代碼,因此我們需要設置下這個配置:
然后我們具體調用的依賴注入的方式需要變化一下:
private readonly ISayHello sayHelloA; private readonly ISayHello sayHelloB; private readonly Func<string, ISayHello> _serviceAccessor; public ValuesController(Func<string, ISayHello> serviceAccessor) { this._serviceAccessor = serviceAccessor; sayHelloA = _serviceAccessor("MultiImpDemoA"); sayHelloB = _serviceAccessor("MultiImpDemoB"); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHelloA.Talk() , sayHelloB.Talk()}; }然后運行看下效果吧:

可以看到A跟B的實現都獲取到了!效果實現!
業務只需要對其中一種實現方式的調用
這時候我們可以根據我們預設的配置來動態獲取我們所需要的實現。這段話說的我自己都感覺拗口。話不多少,開魯吧!這里我將介紹三種實現方式。
-
根據我們的配置文件中設置的key來進行動態的注入。
這種方式實現之前首先得進行相應的配置,如下所示:
"CommonSettings": { "ImplementAssembly": "MultiImpDemo.A" }然后在注入的時候根據配置進行動態的進行注入:
services.AddTransient<ISayHello, A.SayHello>(); services.AddTransient<ISayHello, B.SayHello>();然后在服務調用的時候稍作修改:
private readonly ISayHello sayHello; public ValuesController(IEnumerable<ISayHello> sayHellos,IConfiguration configuration) { sayHello = sayHellos.FirstOrDefault(h => h.GetType().Namespace == configuration.GetSection("CommonSettings:ImplementAssembly").Value); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; }OK,到這里運行一下看下效果吧!然后改下配置文件再看下效果!

-
第二種實現方式,即接口參數的方式這樣可以避免上個方法中反射所帶來的性能損耗。
這種方式是參考汪宇傑公眾號里面的一篇文章,有興趣的可以關注下他的公眾號:“汪宇傑博客”
這里也給出他的博客鏈接:https://edi.wang 經常分享干貨!
這里我們改造下接口,接口中加入一個程序集的屬性,如下所示:public interface ISayHello { string ImplementAssemblyName { get; } string Talk(); }對應的A跟B中的實現代碼也要少做調整:
A:
public string ImplementAssemblyName => "MultiImpDemo.A"; public string Talk() { return "Talk from A.SayHello"; }B:
public string ImplementAssemblyName => "MultiImpDemo.B"; public string Talk() { return "Talk from B.SayHello"; }然后,在實現方法調用的時候稍微修改下:
private readonly ISayHello sayHello; public ValuesController(IEnumerable<ISayHello> sayHellos,IConfiguration configuration) { sayHello = sayHellos.FirstOrDefault(h => h.ImplementAssemblyName == configuration.GetSection("CommonSettings:ImplementAssembly").Value); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; }效果自己運行下看下吧!
-
第三種實現是根據配置進行動態的注冊
首先修改下
ConfigureServices方法:var implementAssembly = Configuration.GetSection("CommonSettings:ImplementAssembly").Value; if (string.IsNullOrWhiteSpace(implementAssembly)) throw new ArgumentNullException("CommonSettings:ImplementAssembly未配置"); if (implementAssembly.Equals("MultiImpDemo.A")) { services.AddTransient<ISayHello, A.SayHello>(); } else { services.AddTransient<ISayHello, B.SayHello>(); }這樣的話就會根據我們的配置文件來進行動態的注冊,然后我們像往常一樣進行服務的調取即可:
private readonly ISayHello _sayHello; public ValuesController(ISayHello sayHello) { _sayHello = sayHello; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { _sayHello.Talk() }; }運行即可得到我們想要的效果!
源碼地址
https://download.csdn.net/download/qin_yu_2010/10917684
注意,源碼有改動,你可以跟着文章把相應的代碼拷貝進來即可運行
總結
本文從具體的業務需求入手,根據需求來或動態的進行對應服務的獲取,或同時使用兩個不同的實現!希望對您有所幫助!如果您有更多的實現方法可以在下方留言,或者加入.NET Core實戰千人群跟637326624大伙進行交流,最后感謝您的閱讀!
