【.NET8.0 新特性系列】依赖注入一对多模式变的超简单

2023-11-22 15:12:27 浏览数 (1)

本系列主要是对.NET8.0和C#12做一些新特性的操作说明,以及对我们平时开发中有影响的一些技术分享。

今天先说一下第一个新特性,就是KeyedService在一对多的依赖注入中的使用(也就是一个接口有多个实现类继承的情况)

以下的代码,是通过原生的依赖注入来讲解的,其他的第三方框架,可以自己自定义扩展。

一、8.0之前我们是如何做

首先,我们就来模拟一下数据:

代码语言:javascript复制
 /// <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";
     }
 }

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

代码语言:javascript复制
 builder.Services.
       AddScoped<IMoreImplService, WelcomeChineseService>();
 builder.Services.
       AddScoped<IMoreImplService, WelcomeEnglishService>();

然后直接调用

这个时候直接预览效果

可以看到是 Welcome ,正好和我们的注入顺序是一致的,你可以把顺序换一下,打印的内容也会发生变化,既然是注入了多个,那就把多个实例都拿出来:

代码语言:javascript复制
 /// <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 控制器里取的时候,只获取了最后一个!

所以我们就可以通过别名的形式给每个实例对象来做个标志。

代码语言:javascript复制
 /// <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 出来实例,然后匹配上别名,那下一步,我们就需要先把这个单例服务给注入进去:

代码语言:javascript复制
 SingletonFactory singletonFactory = new SingletonFactory();
 singletonFactory.AddService<IMoreImplService>(new WelcomeChineseService(), "Chinese");
 singletonFactory.AddService<IMoreImplService>(new WelcomeEnglishService(), "English");

 services.AddSingleton(singletonFactory);

最后我们就来调用看看:

代码语言:javascript复制
// 各自定义需要的多个字段
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、控制器获取单例类,并根据别名获取相对应的服务;

到了这里,就已经完成了,但是这样不是很简洁,可以使用工厂模式,具体的就不再赘述了,大家可以网上搜索下都有,虽然简单工厂的写法比较正规且简单了,但是还是不够优雅,尽管这种一对多的场景不多,但是有时候还是很有必要的,如果都这么写,肯定不行,而且微软官方也想到了这个问题,这不,直接增加了新的特性。

二、8.0新特性KeyedService

还是用上边的例子来改造下注入方法:

代码语言:javascript复制
  builder.Services.
        AddKeyedSingleton<IMoreImplService, WelcomeChineseService>("ch");
  builder.Services.
        AddKeyedSingleton<IMoreImplService, WelcomeEnglishService>("en");

而且也同样的有三个生命周期:

AddKeyedSingleton、AddKeyedScoped、AddKeyedTransient

然后直接调用

代码语言:javascript复制
 [HttpGet]
 public object Get([FromKeyedServices("en")] IMoreImplService moreImplService)
 {
     return moreImplService.SayWelocome();
     //return _moreImplService.SayWelocome();
 }

所以直接三行代码就能搞定,帮我们省了很多的麻烦,

当然如果别名不对,或者为空,就会报错提示服务注册不存在

0 人点赞