1. 程式人生 > >.NET Core中的一個接口多種實現的依賴註入與動態選擇看這篇就夠了

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

str ace rom 由於 參數 創建 性能 類庫 logs

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

作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/10236163.html

代碼演示

在開始實現的方式之前,我們先模擬下代碼。由於真實系統的結構比較復雜,所以這裏我就單獨建一個類似的項目結構代碼。項目如下圖所示:

技術分享圖片

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

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

        public interface ISayHello
        {
            string Talk();
        }

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

  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接口,代碼如下:

    /**
    *┌──────────────────────────────────────────────────────────────┐
    *│ 描    述:                                                    
    *│ 作    者: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實現裏面獲取服務並進行模擬調用:

      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的數據庫實現,需要同時往這兩個數據庫中插入相同的數據。下面分別對這兩種需求進行解決。

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

針對這種情況有如下兩種實現方式:

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

    然後運行起來看下效果吧

    技術分享圖片

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

    然後運行看下效果吧:

    技術分享圖片

    可以看到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,到這裏運行一下看下效果吧!然後改下配置文件再看下效果!

    技術分享圖片

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

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