NET 5 依賴注入多個服務實現類


依賴注入在 ASP.NET Core 中起中很重要的作用,也是一種高大上的編程思想,它的總體原則就是:俺要啥,你就給俺送啥過來。

服務類型的實例轉由容器自動管理,無需我們在代碼中顯式處理。

因此,有了依賴注入后,你的編程思維就得變一變了。

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

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

1、十代單傳模式:一個接口對應一個類,比如先定義接口 IA、IB,隨后,類A實現 IA,類B 實現 IB。一對一。也可以是抽象類(或基類)E,然后 F 繼承 E 類。

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

來,看個例子。

我先定義個接口。

public interface IPlayGame
{
    void Play();
}

然后,寫一個類來實現它。

復制代碼
public class NBPlayGame : IPlayGame
{
    public void Play()
    {
        Console.WriteLine("全民打麻葯。");
    }
}
復制代碼

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

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

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

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 是不是不支持多個服務實現類的注入?這難倒了很多人。

方法一、接收 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());
}
復制代碼

Enumerable<T> 的妙處就是可以 foreach ,這樣你也能訪問多個實例,而且必要時還可以聯合 LINQ 一起耍。

運行結果如下。

 

接口定義

public interface ITest
{
    string Name { get; }
    void Do();
}

public class TestA : ITest
{
    public string Name => "a";
    public void Do() => Console.Write("a");
}

public class TestB : ITest
{
    public string Name => "b";
    public void Do() => Console.Write("b");
}

依賴注入方法

直接注入實現類的方式

services.AddTransient<TestA>();
services.AddTransient<TestB>();

public HomeController(TestA testA)
{
    ......
}

serviceProvider.GetService<TestA>();

使用集合的注入方式

services.AddTransient<ITest, TestA>();
services.AddTransient<ITest, TestB>();

public HomeController(IEnumerable<ITest> tests)
{
    var testa = tests.FirstOrDefault(p => p.Name == "a");
}

serviceProvider.GetServices<ITest>().ToList().FirstOrDefault(p => p.Name == "a");

使用Func工廠的注入方式

注意,注冊Func工廠是一定要使用單例模式

services.AddTransient<TestA>();
services.AddTransient<TestB>();
services.AddSingleton(provider =>
{
    Func<string, ITest> accesor = key =>
    {
        switch (key)
        {
            case “a”:
                return provider.GetService<TestA>();
            case "b":
                return provider.GetService<TestB>();
            default:
                throw new NotSupportedException($"Not Support key : {key}");
        }
    };
    return accesor;
});

public HomeController(Func<string, IOceanOrderHandleStrategy> serviceAccessor)
{
    var testa = serviceAccessor("a");
}

var func = serviceProvider.GetServices<Func<string, IOceanOrderHandleStrategy>>();
var testa = func("a");

使用工廠類注入

同樣要注意,這里注冊只能是單例模式

SingletonFactory singletonFactory = new SingletonFactory();
singletonFactory.AddService<ITest>(new TestA(), "a");
singletonFactory.AddService<ITest>(new TestB(), "b");
services.AddSingleton(singletonFactory);

public HomeController(SingletonFactory singletonFactory)
{
    //使用標識從SingletonFactory獲取自己想要的服務實現
    var testa = singletonFactory.GetService<ITest>("a"); 
}

SingletonFactory.cs

public class SingletonFactory
{

    Dictionary<Type, Dictionary<string, object>> serviceDic;

    public SingletonFactory()
    {
        serviceDic = new Dictionary<Type, Dictionary<string, object>>();
    }

    public void AddService<TService>(string id, TService service) where TService : class
    {
        AddService(typeof(TService), service, id);
    }

    public void AddService(string id, Type serviceType, object service)
    {
        if (service != null)
        {
            if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
            {
                implDict[id] = service;
            }
            else
            {
                implDict = new Dictionary<string, object>();
                implDict[id] = service;
                serviceDict[serviceType] = implDict;
            }
        }
    }


    public TService GetService<TService>(string id) where TService : class
    {
        var serviceType = typeof(TService);
        return GetService<TService>(serviceType, id);
    }

    public TService GetService<TService>(Type serviceType, string id) where TService : class
    {
        if (serviceDic.TryGetValue(serviceType, out Dictionary<string, object> implDic))
        {
            if (implDic.TryGetValue(id, out object service))
            {
                return service as TService;
            }
        }
        return null;
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM