写一个测试项目,但是需要一个DI容器用来测试项目,于是就使用了原生DI容器(也可以使用第三方容器)。
使用如下:
public class Base
{
public static Base Instance { get; private set; }
static Base() {
Instance = new Base();
}
private IServiceProvider provider;
private Base() {
//初始化容器
ServiceCollection services = new ServiceCollection();
//注入对象
services.AddSingleton<ITime , Time>();
//返回服务提供者
provider = services.BuildServiceProvider();
}
//想要用某个注入的服务时,调用该方法
public T Resolve<T>() {
return provider.GetRequiredService<T>();
}
}
进行单元测试:
[TestClass]
public class UnitTest1
{
private ITime time;
public UnitTest1()
{
time = Base.Instance.Resolve<ITime>();
}
[TestMethod]
public void TestMethod1()
{
var result = time.GET();
Assert.AreEqual<string>(result,"DI OK");
}
}
现在遇到了另一个问题,在项目中有如下对象:
IBaseService
ITestOneService : IBaseService
ITestTwoService : IBaseService
TestOne : ITestOneService
TestTwo : ItestTwoService
流程都知道,我们需要注入对象,然后使用。
但是如果一个个对象注入,岂不是很麻烦,而且万一遗漏也容易出错。
如何一次性注入,通过 IBaseService,方法如下:
public static class SelfDI
{
public static IServiceCollection Inject(this IServiceCollection service) {
//service.AddSingleton(typeof( ITestOneService) ,typeof( TestOne));
//service.AddSingleton(typeof(ITestTwoService), typeof(TestTwo));
//或
//service.AddSingleton<ITestOneService, TestOne>();
//service.AddSingleton<ITestTwoService, TestTwo>();
//业务接口都继承于 IBaseService,能否只写一次,就把所有业务类注入
//而不必像上面那样每个业务类都要手动注入,可能遗漏出错。
//类似如下,下面注释是错的,只是为了表达一种想法。
//service.AddSingleton<IBaseService>();
//程序集的所有对象(类或接口),找到实现IBaseService接口的所有 类对象数组(或接口数组)
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(
a =>
a.GetTypes().Where(
t => t.GetInterfaces().Contains(typeof(IBaseService)) && t.IsClass //OR && t.IsInterface
)
)
.ToList();
//假设typeInserface 为找到的接口数组
//可以如下注入
//for (int i = 0; i < types.Count; i++)
//{
// service.AddSingleton(typeInserface[i], types[i]);
//}
//而我们要是想实现上面的想法,需要在调用AddSingleton注入对象时,自动找到与该对象实现的接口才行。
//虽然上面的方法可以找到 接口 ,但是万一 接口数组顺序和类数组顺序不一样,就不行了。
//所以我们还要找个方法,自动找到类实现的接口,如下
for (int i = 0; i < types.Count; i++)
{
//GetInterfaces找到该对象的(实现的接口)
Type[] typeInterface = types[i].GetInterfaces();
service.AddSingleton(typeInterface[0], types[i]);
}
return service;
}
}
注入完之后,直接使用即可。
但是上面的方法有个问题,那就是模型对象需在同一个项目中,如果模型对象不在本项目又该怎么办?
如果模型对象在其他项目,用上面的代码:
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(
a =>
a.GetTypes().Where(
t => t.GetInterfaces().Contains(typeof(IBaseService)) && t.IsClass //OR && t.IsInterface
)
)
.ToList();
是找不到实现了另一个项目的 IBaseService的 任何对象的。
那么要如何寻找?
思路如下:
首先本项目引用 模型所在项目,
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(
a =>
a.GetTypes().Where(
t => t.GetInterfaces().Contains(typeof(IBaseService)) && t.IsClass //OR && t.IsInterface
)
)
.ToList();
这些代码不变,只是IBaseService所在项目变了,这时直接查找会发现查不到。原因如下:
虽然 AppDomain.CurrentDomain.GetAssemblies() 可以获取本应用程序及已加载到此应用程序域的执行上下文中的程序集。
//本项目也已引用模型所在项目,但是下面的结果还是找不到引用的程序集
//是因为还没有使用过(或也没有手动加载过该程序集),如果在之前加上一行代码
//Type p = typeof(IBaseService);
//这样结果中, AppDomain.CurrentDomain.GetAssemblies() 才会 找到模型所在的程序集。
//这样才能找到 该程序集下的 所有对象
所以最终代码如下:
//这里加上一行即可
Type p = typeof(IBaseService);
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(
a =>
a.GetTypes().Where(
t => t.GetInterfaces().Contains(typeof(IBaseService)) && t.IsClass //OR && t.IsInterface
)
)
.ToList();
那一行所在位置也可以替换成如下,是更好的方式。
//手动加载Model.dll 程序集
//var an = AssemblyName.GetAssemblyName("C:/Users/Administrator/Desktop/LiuYan/API/bin/Debug/netcoreapp2.2/Model.dll");
var an = AssemblyName.GetAssemblyName( Directory.GetFiles(AppContext.BaseDirectory , "Model.dll")[0]);
AppDomain.CurrentDomain.Load(an);
//也可以用for循环一次性加载所有程序集,如下:
string[] paths = Directory.GetFiles(AppContext.BaseDirectory, "*.dll");
foreach (var psth in paths) {
var ans = AssemblyName.GetAssemblyName(psth);
AppDomain.CurrentDomain.Load(ans);
}
其它:
获取指定位置的程序集
// 获取该路径的程序集
Assembly ass = Assembly.LoadFrom(@"C:\Users\肖黎望\Desktop\net练习\ConsoleApplication1\Animal\bin\Debug\Animal.dll");
以上按理来说已经可以了,但是最近又出错了,于是想到了一个更健全一点的代码:
所以最终代码如下:
string[] paths = Directory.GetFiles(AppContext.BaseDirectory , "*.dll");
Assembly assembly;
List<Type> types = new List<Type>();
foreach (var path in paths) {
try
{
assembly = Assembly.LoadFrom(path);
var ts = assembly.GetTypes()
.Where(a => a.IsClass && a.GetInterfaces().Contains(typeof(IBaseService)))
.Select(a => a)
.ToList();
types.AddRange(ts);
}
catch {
//可能是GetTypes()出错了
continue;
}
}
foreach (var type in types) {
services.AddSingleton(type.GetInterfaces()[0] , type);
}
这下应该没问题了。记得不同的项目要添加引用,否则会出现找不到的情况出现。