本系列主要是对.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();
}
所以直接三行代码就能搞定,帮我们省了很多的麻烦,
当然如果别名不对,或者为空,就会报错提示服务注册不存在