【.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 有穷性&…

python 学习中遇到的问题(持续更新中)

1、定义一个一维数组,然后利用.T进行转置,利用.T[2]求转置后的数组的第二行。 import numpy as npAnp.array([1,2,3,4,5,6,7,8,9,10,11,12]) BA.reshape(3,4) CB.T DB.T[2] >>> B[[1,2,3,4] [5,6,7,8] [9,10,11,12]] C[[1,5,9] [2,6,10] [3,7,11] [4,8,12] D[3,7…

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中使用它来解决我们的问题并使代码简洁。因此…

python 批量处理图片

将需要处理的图片放在同一个文件夹中 from PIL import Image import os source_path "D:\\work\\test_image\\" target_path "D:\\work\\poly_out\\" image_list os.listdir(source_path) for file in image_list:in_filename source_pathfileimg Im…

python 读取 10 bit YUV 文件

最近博主在做HDR2SDR 的项目&#xff0c;在用python做demo的过程中遇到一个问题&#xff0c;输入是HDR的视频&#xff08;H.265, YUV420P10LE, BT2020&#xff0c; 25fps的MKV文件&#xff09;&#xff0c;由于MKV文件是10bit的&#xff0c;博主直接使用 cv2.VideoCapture() 函…

给 IConfiguration 写一个 GetAppSetting 扩展方法

给 IConfiguration 写一个 GetAppSetting 扩展方法Intro在 .net core 中&#xff0c;微软已经默认使用 appsettings.json 来代替 app.config&#xff0c;并重新设计了一套完整的配置系统&#xff0c;可以支持 json/xml/ini/环境变量等。在 .net core 中有一个 GetConnectionStr…

使用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爱好者社…

C# 结合 Golang 开发

1. 实现方式与语法形式基本方式&#xff1a;将 Go 程序编译成 DLL 供 C# 调用。1.1 Go代码注意&#xff1a;代码中 export 的注释是定义的入口描述不能省略package mainimport "C"import "fmt"func main() { fmt.Println(Test())}var _count 0//Test :…

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…

Linux下管理员权限获取(su和sudo的区别)

我们知道&#xff0c;在Linux下对很多文件进行修改都需要有root&#xff08;管理员&#xff09;权限&#xff0c;比如对/ect/profile等文件的修改。很多情况下&#xff0c;我们在进行开发的时候都是使用普通用户进行登录的&#xff0c;尤其在进行一些环境变量的配置工作时&…

左手专注,右手时间

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

Linux下Java环境变量配置

在Java开发中&#xff0c;在安装完jdk之后&#xff0c;首先需要做的工作就是进行Java环境变量配置。在Windows下的配置我们都比较熟悉&#xff0c;图形化界面配置起来也相对容易&#xff08;详见&#xff1a;Windows7下环境变量配置&#xff09;&#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环境是否配置成功&…