.NET Core中的一個接口多種實現的依賴注入與動態選擇看這篇就夠了

.NET Core中的一個接口多種實現的依賴注入與動態選擇看這篇就夠了

最近有個需求就是一個抽象倉儲層接口方法須要SqlServer以及Oracle兩種實現方式,爲了靈活我在依賴注入的時候把這兩種實現都給注入進了依賴注入容器中,可是在服務調用的時候老是獲取到最後注入的那個方法的實現,這時候就在想能不能實現動態的選擇使用哪一種實現呢?若是能夠的話那麼我只須要在配置文件中進行相應的配置便可獲取到正確的實現方法的調用,這樣的話豈不快哉!今天咱們就來一塊兒探討下實現這種需求的幾種實現方式吧。html

做者:依樂祝
原文地址:http://www.javashuo.com/article/p-vjzpklwc-ez.html數據庫

代碼演示

在開始實現的方式以前,咱們先模擬下代碼。因爲真實系統的結構比較複雜,因此這裏我就單獨建一個相似的項目結構代碼。項目以下圖所示:c#

1546866490439

接下來我來詳細說下上面的結果做用及代碼。api

  1. MultiImpDemo.I 這個項目是接口項目,裏面有一個簡單的接口定義ISayHello,代碼以下:markdown

    public interface ISayHello
        {
            string Talk();
        }

    很簡單,就一個模擬講話的方法。ide

  2. 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";
            }
        }
    }
  3. MultiImpDemo.B 這個類庫項目是接口的另外一種實現方式,裏面也有一個SayHello類用來實現ISayHello接口,代碼以下:post

    /**
    *┌──────────────────────────────────────────────────────────────┐
    *│ 描    述:                                                    
    *│ 做    者: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";
            }
        }
    }
  4. MultiImpDemo.Show 這個就是用來顯示咱們模擬效果的API項目,首選咱們在ConfigureServices中加入以下的代碼來進行上述兩種實現方式的注入:性能

    services.AddTransient<ISayHello, MultiImpDemo.A.SayHello>();
     services.AddTransient<ISayHello, MultiImpDemo.B.SayHello>();
  5. 在api實現裏面獲取服務並進行模擬調用:this

    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'這個接口,結果老是顯示以下的結果:

    1546867091226

兩種需求對應兩種實現

這裏有兩種業務需求!第一種業務中只須要對其中一種實現方式進行調用,如:業務須要SqlServer數據庫的實現就好了。第二種是業務中對這兩種實現方式都有用到,如:業務急須要用到Oracle的數據庫實現同時也有用到SqlServer的數據庫實現,須要同時往這兩個數據庫中插入相同的數據。下面分別對這兩種需求進行解決。

業務中對這兩種實現方式都有用到

針對這種狀況有以下兩種實現方式:

  1. 第二種實現方式

    其實,在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()};
            }

    而後運行起來看下效果吧

    1546870734607

  2. 利用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()};
            }

    而後運行看下效果吧:

    1546869793187

    能夠看到A跟B的實現都獲取到了!效果實現!

業務只須要對其中一種實現方式的調用

這時候咱們能夠根據咱們預設的配置來動態獲取咱們所須要的實現。這段話說的我本身都感受拗口。話很少少,開魯吧!這裏我將介紹三種實現方式。

  1. 根據咱們的配置文件中設置的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,到這裏運行一下看下效果吧!而後改下配置文件再看下效果!

    1546871452531

  2. 第二種實現方式,即接口參數的方式這樣能夠避免上個方法中反射所帶來的性能損耗。

    這裏咱們改造下接口,接口中加入一個程序集的屬性,以下所示:

    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() };
            }

    效果本身運行下看下吧!

  3. 第三種實現是根據配置進行動態的註冊

    首先修改下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大夥進行交流,最後感謝您的閱讀!

posted @ 2019-01-07 22:50 依樂祝 閱讀( ...) 評論( ...) 編輯 收藏