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

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

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

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

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、更直观,更简单;

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

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

  1. .NET Core 2.0迁移技巧之MemoryCache问题修复

    对于传统的.NET Framework项目而言,System.Runtime.Caching命名空间是常用的工具了,其中MemoryCache类则常被用于实现内存缓存. .NET Core 2.0暂时 ...

  2. ASP.Net2.0小技巧 保持滚动条的位置 焦点移动到某个控件 $符号轻松的使用FindControl...

    您可能不知道的ASP.Net2.0小技巧 <!-- 页码和简介 --> 1.  在提交页面之后,保持滚动条的位置 可以在page指令上加上MaintainScrollPositionOnP ...

  3. ASP.NET2.0小技巧--内部控件权限的实现

    谈<内业办公自动化>(可以到http://www.aspx-net.com/测试)的实现技巧之一:     在使用ASP.NET2.0的身份验证和授权时,有时候我们需要提供比页面更高一级的 ...

  4. .NET Core 2.0迁移技巧之web.config配置文件

    大家都知道.NET Core现在不再支持原来的web.config配置文件了,取而代之的是json或xml配置文件.官方推荐的项目配置方式是使用appsettings.json配置文件,这对现有一些重 ...

  5. EntityFramework Core 2.0执行原始查询如何防止SQL注入?

    前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...

  6. ASP.NET Core 2.0 自定义 _ViewStart 和 _ViewImports 的目录位置

    在 ASP.NET Core 里扩展 Razor 查找视图目录不是什么新鲜和困难的事情,但 _ViewStart 和 _ViewImports 这2个视图比较特殊,如果想让 Razor 在我们指定的目 ...

  7. 10个小技巧助您写出高性能的ASP.NET Core代码

    今天这篇文章我们来聊一聊如何提升并优化ASP.NET Core应用程序的性能,本文的大部分内容来自翻译,当然中间穿插着自己的理解,希望对大家有所帮助!话不多说开始今天的主题吧! 我们都知道性能是公共网 ...

  8. core控制器属性注入的用处_.NET Core原生DI扩展之属性注入实现

    在上一篇博客里,我们为.NET Core原生DI扩展了基于名称的注入功能.而今天,我们要来聊一聊属性注入.关于属性注入,历来争议不断,支持派认为,构造函数注入会让构造函数变得冗余,其立意点主要在代码的 ...

  9. NET Core 3.0 AutoFac替换内置DI的新姿势

    .NET Core 3.0 和 以往版本不同,替换AutoFac服务的方式有了一定的变化,在尝试着升级项目的时候出现了一些问题. 原来在NET Core 2.1时候,AutoFac返回一个 IServ ...

最新文章

  1. Python3.5源码分析-内存管理
  2. VS Code 常用插件推荐
  3. QT 建立信号和槽的联系(事件处理)
  4. 被声明为已否决 解决方法
  5. PHP7开启opcache打造强悍性能
  6. ubuntu 创建桌面快捷方式
  7. 缓存-SpringCache-自定义缓存配置
  8. java jdbc操作类_Java-编写一个jdbc操作类
  9. 日志服务数据加工最佳实践: 从其他logstore拉取数据做富化
  10. 接口使用jwt返回token_API接口JWT方式的Token认证(下),客户端(Android)的实现
  11. sqlite 复合唯一索引_SQLite 索引(Index)
  12. ❤️六万字《Spring框架从入门到精通》(建议收藏)❤️
  13. 在linux安装docker以及docker的常用命令(一)
  14. EXCEL 分列功能的使用
  15. 马尔科夫决策过程(MDP) : BlackJack (MC-Off Policy)
  16. 徐波 博士 计算机,徐波医生(博士 广州市第一人民医院主任医师)简介
  17. Git学习笔记使用记录
  18. 中国为什么要买美国国债
  19. css清除浮动float
  20. 2020数学建模国赛B题穿越沙漠个人解析

热门文章

  1. HDOJ1860 ( 统计字符 ) 【水题】
  2. OnIntialDialog() (Dialog应用)和 OnIntialUpdate(View应用)的 程序起始点
  3. JsonRequestBehavior.AllowGet 方便浏览器调试
  4. PHP单例模式(精讲)
  5. Redis集群监控RedisClusterManager
  6. iOS之NSString
  7. javascript理论篇(详情见地址)
  8. [转]order by 多个排序条件
  9. 如何改变X:\Users\XXX的用户名称
  10. [导入]php 安全基础 第八章 共享主机 文件系统浏览