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

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

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

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

程式碼演示

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

1546866490439

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

  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'這個介面,結果總是顯示如下的結果:

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

    然後執行起來看下效果吧

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