• 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;
        }
    }
  • 相关阅读:
    騎士宣言
    [洛谷P1631] 序列合并
    [HNOI2006]公路修建问题
    [洛谷2068] 统计和
    [洛谷P1168] 中位数
    【模板】可持久化数组(可持久化线段树/平衡树)
    【模板】可持久化线段树 1(主席树)
    [JSOI2008]最大数maxnumber
    NOI导刊2010提高(06) 黑匣子
    [洛谷1533] 可怜的狗狗
  • 原文地址:https://www.cnblogs.com/netlock/p/14101029.html
Copyright © 2020-2023  润新知