【.NET Core 3.0】小技巧 || 原生DI一对多注入

本文是一个技巧文章,内容很短,但是被提问的频率很高,所以记录下来,以待大家不时之需。

以下的代码,是通过原生的依赖注入来讲解的,其他的第三方框架,可以自己自定义扩展,效果是一样的,那咱们先来回顾下依赖注入,都有哪几种情况。

 一、依赖注入有哪几种情况

关于依赖注入,其实我已经写了很多的文章,也录了很多的视频了,这里就不过多的解析什么了,无论是原理,还是好处,甚至是使用场景等等,如果你还不是很了解,可以看看我的视频。

https://www.bilibili.com/video/av58096866?p=5

https://www.bilibili.com/video/av73194514

上边的这个是基础和核心知识点,下边的是我直播的时候,手写的代码,可以根据自己的需要去查看。

总体来说,我一直讲的依赖注入的方式,都是面向抽象的 很常见的:一个类对应一个接口。那还有其他的注入情况么?当然还有很多,比如:

1、单独的一个类注入;

2、一个类继承了多个接口;

3、一个接口有多个实现类;

当然,除了上边这三个,还有单独类的 AOP 操作等等,一个类对应一个接口的情况,我们已经说了很多了,这里就不说了,一个类多个接口的,这个也不用说,其实就和一个类对应一个接口,是一个效果,那我们就先说说注入单独的一个类和,一个接口对应多个实现,这两种情况吧。

单独注入一个类很简单,大家都知道,依赖注入,其实就是实例化的过程,然后管理我们的对象的生命周期,降低耦合等等多个好处。

那我们既然是实例化的过程,简单啊,放到容器,直接使用它!

 /// <summary> /// 1、定义一个单独类,不继承任何 /// </summary> public class OneSeparateService {     /// <summary>     /// 写一个方法,可以通过类型返回不同内容     /// </summary>     /// <param name="type"></param>     /// <returns></returns>     public string SayHello(string type="") {
if (type == "English") { return "Hello"; } else {              return "你好"; } } }
// 2、注入容器services.AddScoped<OneSeparateService>();
/// <summary>/// 3、构造函数注入/// </summary>/// <param name="oneSeparateService"></param>public WeatherForecastController(OneSeparateService oneSeparateService){ _oneSeparateService = oneSeparateService;}

 // 4、调用 [HttpGet] public object Get() { // 依赖注入,就等于下边的直接new一个实例 //OneSeparateService oneSeparateService = new OneSeparateService(); return oneSeparateService.SayHello("English"); }

我们只需要直接构造函数注入,即可使用,有种静态方法的意味,是不是很简单!当然很简单啦,因为今天我们不是说这个的,说这个仅仅是一个开胃菜,体会一下注入的过程而已。

好啦,热身完成,下面,我们就详细的说说如何实现一个接口多个实现类吧。

 二、如何注入一对多

既然说到了一对多,那现在我们就来模式一下数据:

 /// <summary> /// 1、定义一个接口 /// </summary> public interface IMoreImplService {     string SayWelocome(); }  /// <summary> /// 2、分别定义两个实现类 /// </summary> public class WelcomeChineseService : IMoreImplService {     public string SayWelocome()     {         return "欢迎";     } }
public class WelcomeEnglishService : IMoreImplService { public string SayWelocome() { return "Welcome"; } }

然后我们准备好了,该注入了,你可能会说,简单呀!直接注入然后调用不就好了:

services.AddScoped<IMoreImplService, WelcomeChineseService>();services.AddScoped<IMoreImplService, WelcomeEnglishService>();

然后直接调用

  public WeatherForecastController(IMoreImplService moreImplService)  {      _moreImplService = moreImplService;  }
[HttpGet] public object Get() { return _moreImplService.SayWelocome();  }

这个时候,是不是有点儿懵,嗯?那我现在到底调用的是哪个实现类呀,我们运行看看效果就知道了:

可以看到是 Welcome ,正好和我们的注入顺序是一致的,就是说,无论注入多少个,最终是最后一个生效,就好像 = new () 了好几次,把之前的实例给覆盖了一样,你应该懂了吧,不信的话,可以把注入的顺序换换,就知道啦。

请记住,刚刚我用的是 好像 字眼,真的是被覆盖掉了么,我们来看看就知道了,既然是注入了多个,我们就把多个实例都拿出来:

 /// <summary> /// 1、将多个接口实例关系全部注入 /// </summary> /// <param name="moreImplServices"></param> public WeatherForecastController(IEnumerable<IMoreImplService> moreImplServices) {     // 注意这里是复数     _moreImplServices = moreImplServices; }
[HttpGet] public object Get() { var result = ""; // 调用多次输出 foreach (var item in _moreImplServices) { result += item.SayWelocome() + "\n"; }
return result; }

详细这个时候,你应该猜得出来答案了吧:

把两个实例都打印了出来,这就说明一个问题,我们在容器里,并不是在注入的时候,后来的把前边的给覆盖掉了,而是 本来容器里就有多个接口实例映射关系 ,只是我们在 controller 控制器里取的时候,只获取了最后一个而已!

那明白了这个问题,我们就很开心了,容器里还是都有的,我们还是可以按照我们的需要,取出想要的某一个,那我们就猜想了,如何区分呢,在文章开头,我们定义方法的时候,就是想着用一个 type ,那这里我们能不能用一个别名来做区分呢,欸,重头戏来了:

 /// <summary> /// 定义一个服务工厂,Singleton注入 /// 注意这个不是真正意义上的工厂,只是提供服务的存取 /// </summary> public class SingletonFactory {     // 定义一个字典,存储我们的接口服务和别名     Dictionary<Type, Dictionary<string, object>> serviceDict;     public SingletonFactory()     {         serviceDict = new Dictionary<Type, Dictionary<string, object>>();     }
/// <summary> /// 根据别名,获取接口实例 /// </summary> /// <typeparam name="TService"></typeparam> /// <param name="id"></param> /// <returns></returns> public TService GetService<TService>(string id) where TService : class { // 获取接口的类型 var serviceType = typeof(TService); // out 方法,先获取某一个接口下的,<别名,实例>字典 if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict)) { // 根据别名,获取接口的实例对象 if (implDict.TryGetValue(id, out object service)) { // 强类型转换 return service as TService; } } return null; }
/// <summary> /// 将实例和别名 匹配存储 /// </summary> /// <typeparam name="TService"></typeparam> /// <param name="service"></param> /// <param name="id"></param> public void AddService<TService>(TService service, string id) where TService : class { var serviceType = typeof(TService); // 对象实例判空 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; } } } }

上边的代码相信应该能大致看的明白,看不明白也没关系,主要一句话概括,就是使用了一个服务,维护一个字典,利用泛型,先把对象实例和别名,分配存储到字典,然后再根据别名或者指定接口的对象实例

就是把接口下的实现类,都 new 出来实例,然后匹配上别名,说白了,就是我们的 type,然后再输出出来。

那下一步,我们就需要先把这个单例服务给注入进去:

 SingletonFactory singletonFactory = new SingletonFactory(); singletonFactory.AddService<IMoreImplService>(new WelcomeChineseService(), "Chinese"); singletonFactory.AddService<IMoreImplService>(new WelcomeEnglishService(), "English");
 services.AddSingleton(singletonFactory);

这个应该都能看的懂,唯一的小问题,可能会问,为啥要把我们的 singletonFactory 给 Singleton 注入?

因为我这是一个对象实例,只能是单例了,而且里边的多个服务因为已经new实例过了,所以没办法控制生命周期

最后我们就来调用看看:

// 各自定义需要的多个字段private readonly IMoreImplService moreImplServiceChinese;private readonly IMoreImplService moreImplServiceEnglish;

public WeatherForecastController(SingletonFactory singletonFactory){ this.singletonFactory = singletonFactory;
// 根据别名获取服务 moreImplServiceChinese = singletonFactory.GetService<IMoreImplService>("Chinese"); moreImplServiceEnglish = singletonFactory.GetService<IMoreImplService>("English");}
[HttpGet("/welcome")]public object GetWelcome(){ return moreImplServiceChinese.SayWelocome();}

结果我们不用看了,已经成功了,最后我们再来回顾一下这种写法的步骤:

1、定义一个单例服务类,将我们的多个对象new出来实例,和别名对应存储起来;

2、将单例类实例化后,注入服务容器;

3、控制器获取单例类,并根据别名获取相对应的服务;

到了这里,我们就已经完成了,是不是到了这里,感觉是已经完成了,但是又感觉哪里不是很舒服,比如这样注入的实例都是单例的,那这样不是很合适呀?

 三、简单工厂模式注入【推荐】

如何才能适应不同的生命周期呢,我这里提供第二个方法:

 // 先把多个实现类服务注入进去 services.AddScoped<WelcomeChineseService>(); services.AddScoped<WelcomeEnglishService>();
// 然后通过简单工厂模式,针对不同的 key 来获取指定的对象实例 services.AddScoped(factory => { Func<string, IMoreImplService> accesor = key => { if (key.Equals("Chinese")) {             // 因为这里是从容器获取服务实例的,所以我们可以控制生命周期 return factory.GetService<WelcomeChineseService>(); } else if (key.Equals("English")) { return factory.GetService<WelcomeEnglishService>(); } else { throw new ArgumentException($"Not Support key : {key}"); } }; return accesor; });

大家可以看一下,我们用的是 Scope 方式注入的,三种生命周期都可以,接下看就是调用了:

 // 将我们的规则 Func 构造函数注入 private readonly Func<string, IMoreImplService> _serviceAccessor;
public WeatherForecastController(Func<string, IMoreImplService> serviceAccessor) { // 获取特定接口的服务访问器,然后根据别名获取 _serviceAccessor = serviceAccessor;     // 这里的别名,你可以配置到 appsetting.json 文件里,动态的修改获取对象实例     // 然后再在接口中配置一个字段 string ImplementKeyName { get; } moreImplServiceChinese = _serviceAccessor("Chinese"); moreImplServiceEnglish = _serviceAccessor("English"); }

[HttpGet("/welcome")] public object GetWelcome() { return moreImplServiceChinese.SayWelocome() + "\n" + moreImplServiceEnglish.SayWelocome(); }

为了演示效果,我把Service服务的构造函数,增加一个动态时间,来判断是否满足Scope需求,那现在我们就来看看效果吧:

 public class WelcomeChineseService : IMoreImplService {     public DateTime Now { get; set; }     public WelcomeChineseService()     {         Now = DateTime.Now;     }     public string SayWelocome()     {         return "欢迎" + Now;     } }


好啦,最后我们来总结一下这个方法的优点:

1、可以实现一个接口对应多个实现类的注入和获取;

2、实例别名可以配置到 appsettings.json 里,动态获取指定服务;

3、可以指定生命周期;

4、更直观,更简单;

虽然这种简单工厂的写法,还是不够优雅,但是毕竟这种一个接口多个实现类的方法本身就不是很优雅,好啦,今天就分享到这里吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312968.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大话数据结构学习笔记二:算法

一 算法定义 算法是解决特定问题求解步骤的描述&#xff0c;在计算机中表现为指令的有限序列&#xff0c;并且每条指令表示一个或多个操作。 二 算法的特性&#xff1a; 1 输入输出&#xff1a;算法具有零个或者多个输入&#xff0c;至少有一个或者多个输出。 2 有穷性&…

ASP.NET Core 3.0 gRPC 身份认证和授权

一.开头聊骚本文算是对于 ASP.NET Core 3.0 gRPC 研究性学习的最后一篇了&#xff0c;以后在实际使用中&#xff0c;可能会发一些经验之文。本文主要讲 ASP.NET Core 本身的认证授权和gRPC接入&#xff0c;认证方式采用目前主流的 JWT 结合 IdentityServer4。二.服务端配置我们…

ASP.NET Core中使用MediatR实现命令和中介者模式

作者&#xff1a;依乐祝原文地址&#xff1a;https://www.cnblogs.com/yilezhu/p/9866068.html在本文中&#xff0c;我将解释命令模式&#xff0c;以及如何利用基于命令模式的第三方库来实现它们&#xff0c;以及如何在ASP.NET Core中使用它来解决我们的问题并使代码简洁。因此…

使用ASP.NET Core 3.x 构建 RESTful API - 3.2 路由

路由机制会把一个请求的URI映射到一个Controller上面的Action&#xff0c;所以当你发送一个HTTP请求的时候&#xff0c;MVC框架会解析这个请求的URI&#xff0c;并尝试着把它映射到一个Controller上面的Action。两个路由中间件在ASP.NET Core 3.x里面&#xff0c;建议使用Endpo…

EFCore批量操作,你真的清楚吗

背景EntityFramework Core有许多新的特性&#xff0c;其中一个重要特性便是批量操作。批量操作意味着不需要为每次Insert/Update/Delete操作发送单独的命令&#xff0c;而是在一次SQL请求中发送批量组合指令。EFCore批量操作实践批处理是期待已久的功能&#xff0c;社区多次提出…

asp.net core 自定义 Policy 替换 AllowAnonymous 的行为

asp.net core 自定义 Policy 替换 AllowAnonymous 的行为Intro最近对我们的服务进行了改造&#xff0c;原本内部服务在内部可以匿名调用&#xff0c;现在增加了限制&#xff0c;通过 identity server 来管理 api 和 client&#xff0c;网关和需要访问api的客户端或api服务相互调…

前端小白在asp.net core mvc中使用ECharts

对于在浏览器中绘制图形图表&#xff0c;目前有较多的js类库可以使用&#xff0c;如&#xff1a;ChartJS&#xff0c;Flot&#xff0c;canvasjs等&#xff0c;但是今天介绍的主角为国产图表库&#xff0c;并在apache孵化&#xff0c;就是大名鼎鼎的echarts。前方高能【官方介绍…

超过1w的Github Star大佬和他们的公众号,太强了!

不少同学担忧的情况——“晋升无望、收入见顶、生活开支飙升、财务危机如影随形”&#xff0c;小编精心挑选了几个优质原创技术号&#xff0c;推荐给大家化解成长的烦恼。他们都是我平时关注的优秀号主&#xff0c;分享给大家&#xff0c;助大家学习路上披荆斩棘~Python爱好者社…

Java和C++区别

Java和C的区别有很多&#xff0c;主要集中在如下几个方面&#xff1a; 运行机制 Java&#xff1a; 编写&#xff1a;是指在Java开发环境&#xff08;Eclipse、Sublime等&#xff09;中进行程序代码的输入&#xff0c;最终形成后缀名为.java的Java源文件。 编译&#xff1a;是指…

未雨绸缪 | 一文简介 Azure Front Door

点击上方蓝字关注“汪宇杰博客”导语昨天早晨微软服务器发生了核爆&#xff0c;Office 365&#xff0c;Bing&#xff0c;Azure DevOps全线完蛋。人类文明危在旦夕之际&#xff0c;微软美国的死士凌晨2点爬起来收福报&#xff0c;修好了服务器&#xff0c;拯救了全人类&#xff…

左手专注,右手时间

大家好&#xff0c;我是Z哥。今天带来的是一篇有感而发的随笔&#xff0c;敬请品尝&#xff5e;前两天最火的事情莫过于WPS上市了。这个平时默默无闻、低调的可怕的产品一下子被捧上了天。我第一次接触到WPS还比较晚&#xff0c;大约在2014年的样子&#xff0c;当时由于某些特殊…

说说开源那些事儿

“ 阅读本文大概需要 9 分钟。 ”前段时间我们遇到了一个情况&#xff0c;课程推送发出来之后有位同学在群里提到&#xff0c;机构提供的项目就是他同学导师的开源项目。之后这位导师在文章评论里联系了我&#xff0c;表示该机构课程涉嫌开源侵权他的 GitHub 开源项目。我们后台…

Linux下Tomcat安装和配置

1、前提&#xff08;JDK环境&#xff09;Tomcat的安装需要JDK环境&#xff0c;如何配置JDK环境见我的文章Linux下Java环境配置&#xff0c;所以在安装Tomcat之前需要先检测JDK环境是否配置好。 进入命令行界面&#xff0c;输入java -version命令来查看JDK环境是否配置成功&…

ASP.NET Core快速入门(第1章:介绍与引入)--学习笔记

点击蓝字关注我们课程链接&#xff1a;http://video.jessetalk.cn/course/explore良心课程&#xff0c;大家一起来学习哈&#xff01;任务1&#xff1a;课程介绍1、介绍与引入2、配置管理3、依赖注入4、ASP.NET Core HTTP介绍5、认证与授权6、ASP.NET Core MVC任务2&#xff1a…

Eclipse下Tomcat服务器配置和使用

在安装完Tomcat之后&#xff0c;在没有进行相关配置时&#xff0c;我们需要到终端下面去启动Tomcat。如果我们在使用Eclipse进行Java开发时&#xff0c;如果能够直接启动Tomcat服务器那将是极为方便&#xff0c;本篇就来讲述Eclipse下Tomcat服务器如何配置以及如何使用。 1、配…

Facebook押注VS Code

Facebook 近日宣布默认采用 VS Code 作为内部开发环境。去年 Facebook 宣布停止维护其开源的 Nuclide/Atom-IDE&#xff0c;以及相关的一些库。Nuclide/Atom-IDE 是 Atom 编辑器的功能包&#xff0c;其通过语言服务器协议与自定义语言服务器交互&#xff0c;为 Atom 提供一系列…

Linux下软件安装和卸载

在Windows下&#xff0c;我们安装软件基本上是先到网上下载软件的安装包&#xff0c;通常是以.exe和.msi为后缀的文件&#xff0c;然后双击安装包&#xff0c;之后就可以按照软件安装向导一步步进行安装了&#xff0c;但在Linux下&#xff0c;软件的安装方式截然不同。总的来说…

为WPF, UWP 及 Xamarin实现一个简单的消息组件

友情提示&#xff1a;阅读本文大概需要8分钟。欢迎大家点击上方公众号链接关注我&#xff0c;了解新西兰码农生活本文目录&#xff1a;1. 介绍2. Message - 消息3. Subscription - 订阅4. MessageHub - 消息总线4.1 Subscribe - 订阅4.2 Unsubscribe - 取消订阅4.3 Publish - 发…

Mac(OS X)安装、配置并使用MySQL数据库

1、MySQL安装包下载 MySQL Community Server下载地址&#xff1a;http://dev.mysql.com/downloads/mysql/&#xff0c;下载完毕之后安装文件如下所示&#xff1a; 2、MySQL安装 双击安装文件进行安装&#xff0c;出现如下界面&#xff1a; 双击窗体中的“mysql-5.7.10-osx10.9-…

用ASP.NETCore构建可检测的高可用服务

一、前言2019 中国 .NET 开发者峰会&#xff08;.NET Conf China 2019&#xff09;于2019年11月10日完美谢幕&#xff0c;校宝在线作为星牌赞助给予了峰会大力支持&#xff0c;我和项斌等一行十位同事以讲师、志愿者的身份公司参与到峰会的支持工作中&#xff0c;我自己很荣幸能…