概要:因为不知道写啥,所以随便找个东西乱说几句,嗯,就这样,就是这个目的。

1.IOC是啥呢?

  IOC - Inversion of Control,即控制反转的意思,这里要搞明白的就是,它是一种思想,一种用于设计的方式(DI)(DI 是手段),(并不是前几天园子中刚出的一片说是原则),OO原则不包含它,再说下,他不是原则!!原则指的是:依赖倒置(Dependency Inversion Principle, DIP)。

  那么,既然是控制反转,怎么反转的?对吧,说到重点了吧。很简单,通过一个容器,将对象注册到这个容器之后,由这个容器来创建对象,从而免去了手动创建对象以及创建后对象(资源)的获取。

  你可能又会问,有什么好处?我直接new不也一样的吗?对的,你说的很对,但是这样做必然导致了对象之间的耦合度增加了,既不方便测试,又不方便复用;IOC却很好的解决了这些问题,可以i很容易创建出一个松耦合的应用框架,同时更方便于测试。

  常用的 IOC工具有 Autofac,castle windsor,unit,structMap等,本人使用过的只有 autofac,unit,还有自带的mef,,,也能算一个吧,还有现在的core的 DependencyInJection。

2.Core中的DI是啥?

依赖注入的有三种方式:属性,构造函数,接口注入;

  在core之前,我们在.net framework中使用 autofac的时候,这三种方式我们可以随意使用的,比较方便(因为重点不是在这,所以不说core之前),但是有一点是,在web api和 web中属性注入稍微有点不同,自行扩展吧。

  core中我们使用DI(dependency injection)的时候,属性注入好像还不支持,所以跳过这个,我们使用更多的是通过自定义接口使用构造函数注入。下面会有演示。

  演示前我们先弄清楚 core中的这个 dependencyInJection到底是个啥,他是有啥构成的。这是git上提供的源码:https://github.com/aspnet/DependencyInjection,但是,,那么一大陀东西你肯定不想看,想走捷径吧,所以这里简要说一下,看图:

当我们创建了一个core 的项目之后,我们,会看到 startUp.cs的ConfigureServices使用了一个IServiceCollection的参数,这个东西,就是我们1中所说的IOC的容器,他的构成如图所示,是由一系列的ServiceDescriptor组成,是一个集合对象,而本质上而言,

ServiceDescriptor也是一个容器,其中定义了对象了类型以及生命周期,说白了控制生命周期的,也是有他决定的(生命周期:Scoped:本次完整请求的生命,Singleton:伴随整个应用程序神一样存在的声明,Transient:瞬时声明,用一下消失)。

另外,我们还会见到另一个东西,IServiceProvider,这个是服务的提供器,IServiceCollection在获取一系列的ServiceDescriptor之后其实他还是并没有创建我们所需要的实现对象的,比如AssemblyFinder实现了IAssemblyFinder的接口,此时我们只是将其加入IserviceCollection,

直接使用IAssemblyFinder获取到的一定是null对象,这里是通过BuildServiceProvider()这个方法,将这二者映射在一起的,此时这个容器才真正的创建完成,这时候我们再使用 IAssemblyFinder的时候便可以正常。这个动作好比是我们自己通过Activator 反射创建某个接口的实现类,

当然其内部也是这个样的实现道理。如果需要更深入见这篇文章:https://www.cnblogs.com/cheesebar/p/7675214.html

好了,扯了这么多理论,说一千道一万不如来一个实战,下面就看怎么用。

3.怎么用?

  首先我们先快速创建一个项目,并创建ICustomerService接口和CustomerServiceImpl实现类,同时在startup.cs的ConfigureService中注册到services容器:

  

  测试代码: 

public interface ICustomerService : IDependency
{
Task<string> GetCustomerInfo();
}
public class CustomerServiceImpl : ICustomerService
{
public async Task<string> GetCustomerInfo()
{
return await Task.FromResult("放了一年的牛了");
}
}
startUp.cs的ConfigureService中注册到容器:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ICustomerService, CustomerServiceImpl>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
//controller中的注入
private readonly ICustomerService _customerService;
public ValuesController(ICustomerService customerService)
{
_customerService = customerService;
} // GET api/values
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> Get()
{
return new string[] { await _customerService.GetCustomerInfo()};
}

  然后我们在controller中注入并查看结果:

  

  这就实现了我们想要的效果了,这个比较简单,入门都算不上。

  可以看到我们注册到services容器中的时候,是一一对应写入的(services.AddTransient<ICustomerService, CustomerServiceImpl>();),但是实际开发会有很多个接口和接口的实现类,多个人同时改这个文件(startup.cs),那还不坏事儿了嘛,集体提交肯定出现冲突或者重复或者遗漏.对吧,而且不方便测试;所以:怎么优雅一点?

4.怎么优雅的使用?

   实际开发过程中,我们不可能去手动一个一个的注入的,除非你项目组就你一个人。所以,需要实现按自动注入,还是就上面的测试,再加一个 IProductService和和他的实现类ProductServiceImpl,

public interface IProductService 
{
Task<string> GetProductInfo();
}

public class ProductServiceImpl : IProductService
{
public async Task<string> GetProductInfo()
{
return await Task.FromResult("我是一个产品");
}
}

同时controller中修改下:

private readonly ICustomerService _customerService;
private readonly IProductService _productService;
public ValuesController(ICustomerService customerService, IProductService productService)
{
_customerService = customerService;
_productService = productService;
} // GET api/values
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> Get()
{
return new string[] { await _customerService.GetCustomerInfo(), await _productService.GetProductInfo() };
}

  实现自动注入就需要有一个对象的查找的依据,也就是一个基对象,按照我们以往使用Autofac的习惯,我们会定义一个IDependency接口:好,那我们就定义一个

public interface IDependency
{
}

  然后修改 ICustomerService和IProductService的接口,都去继承这个IDependency.

  修改完成后重点来了,怎么通过Idependency获取的呢?像autofac的使用一样?当然已经有dependencyInjection的扩展插件可以支持 scan对应的依赖项并注册到IOC ,但是毕竟是人家的东西,所以我们自己搞搞。也好解决,我们获取到当前应用的依赖项,然后找到Idependecy对应的实现对象(类),

  1).使用 DependencyContext 获取当前应用的依赖项:

  该对象在Microsoft.Extensions.DependencyModel空间下,可以通过 DependencyContext.Default获取到当前应用依赖的所有对象(dll),如下实现:

DependencyContext context = DependencyContext.Default;
string[] fullDllNames = context
.CompileLibraries
.SelectMany(m => m.Assemblies)
.Distinct().Select(m => m.Replace(".dll", ""))
.ToArray();

  因为我们下面将使用Assembly.Load(dll的名称)加载对应的dll对象,所以这里把后缀名给替换掉。

  但是这里有个问题,这样获取到的对象包含了 微软的一系列东西,不是我们注入所需要的,或者说不是我们自定义的对象,所以需要过滤掉。不必要的对象包含(我在测试时候大致列出来这几个)

string[] 不需要的程序及对象 =
{
"System",
"Microsoft",
"netstandard",
"dotnet",
"Window",
"mscorlib",
"Newtonsoft",
"Remotion.Linq"
};

  所以我们再过滤掉上面这几个不需要的对象

//只取对象名称
List<string> shortNames = new List<string>();
fullDllNames.ToList().ForEach(name =>
{
var n = name.Substring(name.LastIndexOf('/') + );
if (!不需要的程序及对象.Any(non => n.StartsWith(non)))
shortNames.Add(n);
});

  最后,就是使用Assembly.Load加载获取并过滤之后的程序集对象了,同时获取到IDependency的子对象的实现类集合对象(types)

List<Assembly> assemblies = new List<Assembly>();
foreach (var fileName in shortNames)
{
AssemblyName assemblyName = new AssemblyName(fileName);
try { assemblies.Add(Assembly.Load(assemblyName)); }
catch { }
}
var baseType = typeof(IDependency);
Type[] types = assemblies.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsClass && baseType.IsAssignableFrom(type)).Distinct().ToArray();

此时再看我们的使用效果:

在跟目录再新增一个以来扩展类:其中的实现就是上面说的获取程序及以及注册到services容器:

这里也就是上面说的 完整的代码:

public static class DependencyExtensions
{
public static IServiceCollection RegisterServices(this IServiceCollection services)
{
//之前的实现,
//services.AddTransient<ICustomerService, CustomerServiceImpl>();
//services.AddTransient<IProductService, ProductServiceImpl>(); //现在的实现
Type[] types = GetDependencyTypes();
types?.ToList().ForEach(t =>
{
var @interface = t.GetInterfaces().Where(it => it.GetType() != typeof(IDependency)).FirstOrDefault();
//services.AddTransient(@interface.GetType(), t.GetType());
services.AddTransient(@interface.GetTypeInfo(),t.GetTypeInfo());
}); return services;
} private static Type[] GetDependencyTypes()
{
string[] 不需要的程序及对象 =
{
"System",
"Microsoft",
"netstandard",
"dotnet",
"Window",
"mscorlib",
"Newtonsoft",
"Remotion.Linq"
}; DependencyContext context = DependencyContext.Default;//depnedencyModel空间下,如果是 传统.netfx,可以使用 通过 Directory.GetFiles获取 AppDomain.CurrentDomain.BaseDirectory获取的目录下的dll及.exe对象;
string[] fullDllNames = context
.CompileLibraries
.SelectMany(m => m.Assemblies)
.Distinct().Select(m => m.Replace(".dll", ""))
.ToArray(); //只取对象名称
List<string> shortNames = new List<string>();
fullDllNames.ToList().ForEach(name =>
{
var n = name.Substring(name.LastIndexOf('/') + );
if (!不需要的程序及对象.Any(non => n.StartsWith(non)))
shortNames.Add(n);
}); List<Assembly> assemblies = new List<Assembly>();
foreach (var fileName in shortNames)
{
AssemblyName assemblyName = new AssemblyName(fileName);
try { assemblies.Add(Assembly.Load(assemblyName)); }
catch { }
}
var baseType = typeof(IDependency);
Type[] types = assemblies.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsClass && baseType.IsAssignableFrom(type)).Distinct().ToArray();
return types;
}
}

注:获取程序集的这个实现可以单独放到一个类中实现,然后注册成为singleton对象,同时在该类中定义一个私有的 几何对象,用于存放第一次获取的对象集合(types),以后的再访问直接从这个变量中拿出来,减少不必要的资源耗费和提升性能。

此时startup.cs中只需要一行代码就好了:

services.RegisterServices();

看结果:

过滤之后的仅有我们定义的两个对象。

  

5.怎么更优雅的使用?

  以上只是获取我们开发过程中使用的一些业务或者逻辑实现对象的获取,集体开发的时候 假设没人或者每个小组开发各自模块时候,创建各自的应用程序及对象(类似模块式或插件式开发),上面那样岂不是不能满足了?每个组的每个模块都定义一次啊?不现实是吧。比如:如果按照DDD的经典四层分层的话,身份验证或者授权 功能的实现应该是在基础设施曾(Infrastructure)实现的,再比如 如果按照聚合边界的划分,不同域可能是单独一个应用程序集包含独自的上下文对象,那么个开发人员开发各自模块,这时候就需要一个统一的DI注入的约束了,否则将会变得很乱很糟糕。所以我们可以将这些独立的模块可统称为Module模块,用谁就注入谁。

  如下(模块的基类):

public abstract class Module
{
/// <summary>
/// 获取 模块启动顺序,模块启动的顺序先按级别启动,同一级别内部再按此顺序启动,
/// 级别默认为0,表示无依赖,需要在同级别有依赖顺序的时候,再重写为>0的顺序值
/// </summary>
public virtual int Order => ;
public virtual IServiceCollection RegisterModule(IServiceCollection services)
{
return services;
} /// <summary>
/// 应用模块服务
/// </summary>
/// <param name="provider">服务提供者</param>
public virtual void UseModule(IServiceProvider provider)
{
}
}

  定义一个注入的使用基对象,其中包含了两个个功能:

  1).将当前模块涉及的依赖项注册到services容器的功能;

  2).在注册到容器的对象中包含部分方法需要被调用之后才能初始化的对象(资源)方法,该方法将在startUp.cs的Configure方法中使用,类似UseMvc();

  3).一个用于标记注入到services容器的先后顺序的标识。

  这个Order存在的必要性?是很有必要的,比如在开发过程中,每个模块独立开发需要独立的上下文对象,那么岂不是要每个模块都创建一次数据库连接配置,蛋疼吧?所以,可以将上下文访问单独封装,比如我们惯用的仓储对象,和工作单元,用来提供统一的上下文访问入口(iuow),以及统一的领域对象的操作方法(irepository-CURD),然后将iuow注入到各个模块以便获取上下文对象。那么这就有个先后顺序了,肯定要先注册这个仓储和工作单元所在的程序集(module),其次是每个业务的插件模块。

  接下来就是定义这个Module的查找器,其实和上面 4 中的类似,只是需要将 basetType(IDependency替换成 Module即可),assembly的过滤条件换成 type => type.IsClass &&!type.IsAbstract && baseType.IsAssignableFrom(type)

  当然,这里的IsAssignableFrom是针对非泛型对象的,如果是泛型对象需要单独处理下,如下,源码来自O#:

/// <summary>
/// 判断当前泛型类型是否可由指定类型的实例填充
/// </summary>
/// <param name="genericType">泛型类型</param>
/// <param name="type">指定类型</param>
/// <returns></returns>
public static bool IsGenericAssignableFrom(this Type genericType, Type type)
{
genericType.CheckNotNull("genericType");
type.CheckNotNull("type");
if (!genericType.IsGenericType)
{
throw new ArgumentException("该功能只支持泛型类型的调用,非泛型类型可使用 IsAssignableFrom 方法。");
} List<Type> allOthers = new List<Type> { type };
if (genericType.IsInterface)
{
allOthers.AddRange(type.GetInterfaces());
} foreach (var other in allOthers)
{
Type cur = other;
while (cur != null)
{
if (cur.IsGenericType)
{
cur = cur.GetGenericTypeDefinition();
}
if (cur.IsSubclassOf(genericType) || cur == genericType)
{
return true;
}
cur = cur.BaseType;
}
}
return false;
}

这时候假设我们有 A:订单模块,B:支付模块, C:收货地址管理模块,D:(授权)验证模块 等等,每个模块中都会单独定义一个继承自Module这个抽象对象的子类,每个子对象中注册了各自的模块所需的依赖对象到容器中,这时候我们只需要在 应用层(presentation layer)的 startup.cs中将模块注入即可:

修改 4 中获取程序及对象的方法 获取模块(Module)之后依次注入模块:

var moduleObjs = 通过4 中的方法获取到的程序集对象(Module);
modules = moduleObjs
.Select(m => (Module.Module)Activator.CreateInstance(m))
.OrderBy(m => m.Order);
foreach (var m in modules)
{
services = m.RegisterModule(services);
Console.WriteLine($"模块:【{m.GetType().Name}】注入完成");
}
return services;

  如果运行项目的效果基本如下:

  

6.最后

  偷懒了,篇幅有点长了,写多了耗费太多时间了,,

.net core 中的 DependencyInjection - IOC的更多相关文章

  1. ASP.NET Core中使用IOC三部曲(一.使用ASP.NET Core自带的IOC容器)

    前言 本文主要是详解一下在ASP.NET Core中,自带的IOC容器相关的使用方式和注入类型的生命周期. 这里就不详细的赘述IOC是什么 以及DI是什么了.. emm..不懂的可以自行百度. 目录 ...

  2. ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)

    前言 本文主要是详解一下在ASP.NET Core中,自带的IOC容器相关的使用方式和注入类型的生命周期. 这里就不详细的赘述IOC是什么 以及DI是什么了.. emm..不懂的可以自行百度. 目录 ...

  3. 大话DI依赖注入+IOC控制反转(二) 之 浅析.Net Core中的DI与IOC

      转发时请注明原创作者及地址,否则追究责任.原创:alunchen 在上一篇文章中,我们聊了很多关于定义的方面,比较孤燥,下面我们结合.Net Core聊一下依赖注入&控制反转. 三种对象生 ...

  4. 浅谈ASP.NET Core中IOC与DI的理解和使用

    说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...

  5. .Net Core中IOC容器的使用

    打代码之前先说一下几个概念,那就是什么是IOC.DI.DIP 虽然网上讲这些的已经有很多了,我这里还是要再赘述一下 IOC容器就是一个工厂,负责创建对象的 IOC控制反转:只是把上端对下端的依赖,换成 ...

  6. 【asp.net core 系列】14 .net core 中的IOC

    0.前言 通过前面几篇,我们了解到了如何实现项目的基本架构:数据源.路由设置.加密以及身份验证.那么在实现的时候,我们还会遇到这样的一个问题:当我们业务类和数据源越来越多的时候,我们无法通过普通的构造 ...

  7. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  8. ASP.NET Core中使用IOC三部曲(三.采用替换后的Autofac来实现AOP拦截)

    前言 本文主要是详解一下在ASP.NET Core中,采用替换后的Autofac来实现AOP拦截 觉得有帮助的朋友~可以左上角点个关注,右下角点个推荐 这里就不详细的赘述IOC是什么 以及DI是什么了 ...

  9. .net core中使用autofac进行IOC

    .net Core中自带DI是非常简单轻量化的,但是如果批量注册就得扩展,下面使用反射进行批量注册的 public void AddAssembly(IServiceCollection servic ...

随机推荐

  1. python中的os.listdir()函数

    os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表.这个列表以字母顺序. 它不包括 '.' 和'..' 即使它在文件夹中. 只支持在 Unix, Windows 下使用. ...

  2. hibernate框架学习之多表查询helloworld

    package cn.itcast.h3.hql; import java.util.List; import org.hibernate.Query; import org.hibernate.Se ...

  3. node+vue报错合辑

    vue 1.This is probably not a problem with npm. There is likely additional logging output above. npm ...

  4. bootstrap简单使用布局、栅格系统、modal标签页等常用组件入门

    <!DOCTYPE html> <html> <head> <title>bootstrap</title> <!-- 引入boots ...

  5. mysql alter 效率

    2017年9月15日 10:36:54 星期五 今天遇到一个效率问题记下来: 场景: mysql要更改一下表字段的注释, 因为sql语句问题, 导致更新了整张表.. 错误: ) UNSIGNED ' ...

  6. Razor视图基本语法

    <!--Razor C#--> @for (int i = 0; i < 10; i++) {     <baobao>good</baobao> } < ...

  7. Linux命令之control快捷键组合

    ***********************Linux系统中的一些control快捷键组合汇总如下************************ ctrl+a:光标移到行首. ctrl+b:光标左 ...

  8. Java感情

    不知道怎么会执着于这一门语言.论速度的话,c那系更好一些,而且对底层的操控更多是c那边的.还是想通过Java做这些事. 对Java不怎么懂,它的底层实现是怎样的.只知道一个大体的思想,不知道具体步骤. ...

  9. Confluence 6 数据库字符集编码和问题

    数据库字符集编码 你的数据库和 JDBC 数据源连接必须配置为使用 UTF-8 编码(或者根据你配置的数据库来制定正确的 UTF-8 编码字符集,例如在 Oracle 中使用的是 AL32UTF8 ) ...

  10. LoadRunner学习笔记

    什么是性能测试: 简单说,功能测试是软件是否能用,性能测试是看软件好不好用: 性能测试的含义,大体来讲就是通过自动化的手段,模拟生产运行的业务压力或者相应的场景,来测试协同系统是否满足生产需要. 性能 ...