【ASP.NET Core】依賴注入高級玩法——如何注入多個服務實現類

依賴注入在 ASP.NET Core 中起中很重要的做用,也是一種高大上的編程思想,它的整體原則就是:俺要啥,你就給俺送啥過來。服務類型的實例轉由容器自動管理,無需咱們在代碼中顯式處理。編程

所以,有了依賴注入後,你的編程思惟就得變一變了。在過去,許多功能性的類型(好比一個加密解密的類),咱們都喜歡將其定義爲靜態(static),而有了依賴注入,你就要避免使用靜態類型,應該交由服務容器幫你管理,只要你用好了,你會發現依賴注入是很方便的。服務器

依賴注入的初級玩法,也是比較標準的玩法,此種玩法有兩種模式:app

一、十代單傳模式:一個接口對應一個類,好比先定義接口 IA、IB,隨後,類A實現 IA,類B 實現 IB。一對一。也能夠是抽象類(或基類)E,而後 F 繼承 E 類。async

二、斷子絕孫模式:直接就寫一個類,不考慮派生,直接就添加到服務容器中。ide

 

來,看個例子。函數

我先定義個接口。ui

    public interface IPlayGame
    {
        void Play();
    }

而後,寫一個類來實現它。加密

    public class NBPlayGame : IPlayGame
    {
        public void Play()
        {
            Console.WriteLine("全民打麻藥。");
        }
    }

 

咱們知道,所謂服務類,其實就是普通類,這些類通常用於完成某些功能,好比計算 MD5 值。接着呢,還記得 Startup 類有個 ConfigureServices 方法吧,對,就在這廝裏面把咱們剛剛那個服務進行註冊(就是添加到 ServiceCollection 集合中)。spa

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IPlayGame, NBPlayGame>();
        }

添加的時候很簡單,類型一對一,IPlayGame 接口與 NBPlayGame 類對應。添加時有三種方法你能夠調用,實際上對應着,服務類在容器中的生命週期。code

AddSingleton:單個實例,這是壽命最長的,與天同壽。整個應用程序中僅用一個實例。

AddTransient:這個是最短命的,多是每天晚上加班熬夜,死得很快。此種狀況下,服務類的實例是用的時候建立,用完後直接銷燬。

AddScoped:這個比較難理解。它的生命週期在單個請求內,包括客戶端與服務器之間隨後產生的子請求,反正只要請求的會話結束了,就會清理。

 

而後,你就能夠進行注入了,好比在中間件,在控制器,或者在其餘服務類的構造函數上(中間件是在 Invoke / InvokeAsync 方法上)進行實例接收。

如今來用一下,寫一箇中間件。

    public class TestMiddleware
    {
        public TestMiddleware(RequestDelegate next) { }

        public Task InvokeAsync(HttpContext context, IPlayGame game)
        {
            game.Play();
            return Task.CompletedTask;
        }
    }

已註冊的服務會注入到 InvokeAsync 方法的參數中。注意第一個參數是 HttpContext,這是必須參數,後面的是注入的參數。

最後,在 Startup 類的 Configure 方法中就能夠 use 這個中間件了。

        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<TestMiddleware>();
        }

 

運行後,Play 方法調用,在控制檯中輸出如下結果。

 

 「斷子絕孫」模式,不使用接口規範,直接寫功能類。

    public class DoSomething
    {
        public string GetMessage() => "你好,剛纔 Boss 找你。";
    }

註冊服務時更簡單。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<DoSomething>();
        }

在 Configure 方法中進行注入。

        public void Configure(IApplicationBuilder app, DoSomething thing)
        {
            Console.WriteLine(thing.GetMessage());
        }

運行後,輸出結果以下。

 

 在容器中,使用 ServiceDescriptor 類來存儲服務類型相關的信息。其中,ServiceType 表示的是服務的類型,若是服務是有接口與實現類的,那麼這個屬性指的是接口的類型,實現類的類型信息由 ImplementationType 屬性存儲。若是沒有接口,直接只定義類型,那麼這個類型的信息就存到 ServiceType 屬性上,ImplementationType 屬性不使用。

上面這些例子中,ServiceType 是 IPlayGame 接口相關信息,ImplementationType 是 NBPlayGame 類的信息。若是像上面 DoSomething 類的狀況,則 ServiceType 爲 DoSomething 相關的信息,ImplementationType 爲空。

 

 

接下來,我們看高級玩法。

定義一個接口。

    public interface IDemoService
    {
        string Version { get; }
        void Run();
    }

而後,有兩個類實現這個接口。

    public class DemoService1 : IDemoService
    {
        public string Version => "v1";

        public void Run()
        {
            Console.WriteLine("第一個服務實現類。");
        }
    }

    public class DemoService2 : IDemoService
    {
        public string Version => "v2";

        public void Run()
        {
            Console.WriteLine("第二個服務實現類。");
        }
    }

 

而後,咱們註冊服務。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IDemoService, DemoService1>();
            services.AddTransient<IDemoService, DemoService2>();
        }

而後咱們照例,接收注入,我們依舊使用中間件的方法參數接收。

    public class DemoMiddleware
    {
        public DemoMiddleware(RequestDelegate next)
        {
            // 因爲程序約定,此構造函數必須提供。
        }

        public async Task InvokeAsync(HttpContext context, IDemoService sv)
        {
            await context.Response.WriteAsync(sv.Version);
        }
    }

而後,在 Startup.Configure 方法中使用該中間件。

        public void Configure(IApplicationBuilder app, DoSomething thing)
        {
            app.UseMiddleware<DemoMiddleware>();
        }

運行以後,你發現問題了,看看輸出。

 

 出事了,參數僅能接收到最後註冊的實現類型實例,也就是 DemoService2 類。因此就看到網上有很多朋友發貼問了,.NET Core 是否是不支持多個服務實現類的注入?這難倒了不少人。

實話告訴你,Core Core 兄是支持注入多個實現類的實例的。

下面,老周介紹兩種解決方法(其實有三種,還有一種不太好弄,尤爲是你對 Core 兄不熟的時候,因此我說兩種,基本夠用)。

方法1、接收 IServiceProvider 類型的注入。

        public async Task InvokeAsync(HttpContext context, IServiceProvider provider)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var sv in provider.GetServices<IDemoService>())
            {
                sb.Append($"{sv.Version}<br/>");
            }
            await context.Response.WriteAsync(sb.ToString());
        }

只要能接收到 IServiceProvider 所引用的實例,就能經過 GetServices 方法獲取多個服務實例。

 

方法二,這種方法老周很推薦,更簡單,直接注入 IEnumerable<T> 類型,本例中就是 IEnumerable<IDemoService>。

        public async Task InvokeAsync(HttpContext context, IEnumerable<IDemoService> svs)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var sv in svs)
            {
                sb.Append($"{sv.Version}<br/>");
            }
            await context.Response.WriteAsync(sb.ToString());
        }

IEnumerable<T> 的妙處就是能夠 foreach ,這樣你也能訪問多個實例,並且必要時還能夠聯合 LINQ 一塊兒耍。

運行結果以下。

 

 

不要問我是怎麼發現的,反正我告訴你了,你用就是了。

好了,今天的話題就到這兒了,3166。

相關文章
相關標籤/搜索