最近有個需求就是一個抽象倉儲層介面方法需要SqlServer以及Oracle兩種實現方式,為了靈活我在依賴註入的時候把這兩種實現都給註入進了依賴註入容器中,但是在服務調用的時候總是獲取到最後註入的那個方法的實現,這時候就在想能不能實現動態的選擇使用哪種實現呢?如果可以的話那麼我只需要在配置文件中進行 ...
最近有個需求就是一個抽象倉儲層介面方法需要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,到這裡運行一下看下效果吧!然後改下配置文件再看下效果!
第二種實現方式,即介面參數的方式這樣可以避免上個方法中反射所帶來的性能損耗。
這裡我們改造下介面,介面中加入一個程式集的屬性,如下所示:
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() }; }
運行即可得到我們想要的效果!
總結
本文從具體的業務需求入手,根據需求來或動態的進行對應服務的獲取,或同時使用兩個不同的實現!希望對您有所幫助!如果您有更多的實現方法可以在下方留言,或者加入.NET Core實戰千人群跟637326624大伙進行交流,最後感謝您的閱讀!