Autofac 框架初识与应用
一、前言
主要讲述了什么是IoC容器,以及了解到它是DI构造函注入的框架,它管理着依赖项的生命周期以及映射关系,同时也介绍实践了在ASP.Net Core中,默认提供的内置IoC容器,以及它的实例注册方式和相应的生命周期。
但考虑到在实际项目中,如果需要一个个添加实例,会略显麻烦,为了达到可以简化我们工作量,因此我们也可以引入其他的Ioc容器框架,实现更多的功能和扩展。
这里选择用Autofac,这也是在.net下比较流行的,其他的框架不做说明,可自行查阅了解。
二、说明
AutoFac是一个开源的轻量级的依赖注入容器,也是.net下比较流行的实现依赖注入的工具之一。
将Autofac整合到你的应用的基本流程如下:
- 按照 控制反转 (IoC) 的思想构建你的应用.
- 添加Autofac引用.
- 在应用的 startup 处
- 创建 ContainerBuilder.
- 注册组件.
- 创建容器,将其保存以备后续使用.
- 应用执行阶段
- 从容器中创建一个生命周期.
- 在此生命周期作用域内解析组件实例.
三、开始
3.1 默认容器
在上一篇中定义的三个接口,分别测试Singleton,Scope,Transient三种,一个 TestService服务,
在内置的IoC容器中,在Startup.cs类文件ConfigureServices方法中,注入依赖方式如下:
其他不清楚的可以回看上一篇说明
3.2 Autofac框架
现在我们使用其他的IoC容器框架来替换默认的内置IoC,这里选择使用Autofac框架
.net core 2.x和3.x 使用autofac注入方式不一样,此文章是针对.net core 3.x
首先,我们需要从nuget引用相关的包.
Autofac.Extensions.DependencyInjection(这个包扩展了一些微软提供服务的类.来方便替换autofac)
然后在Program.cs 新增一行代码
UseServiceProviderFactory 设置工厂来替换实例。
然后在Startup类增加ConfigureContainer方法,在方法中注入依赖:
说明
ASP.NET Core 引入了具有强类型容器配置的能力。 它提供了一个ConfigureContainer方法,您可以使用Autofac单独注册,而不是使用ServiceCollection注册。
使用ConfigureContainer配置
- 在配置WebHostBuilder的Program.Main方法中,调用AddAutofac将Autofac挂钩到启动管道中。
- 在Startup类的ConfigureServices方法中,使用其他库提供的扩展方法将内容注册到IServiceCollection中。
- 在Startup类的ConfigureContainer方法中,将内容直接注册到AutofacContainerBuilder中。
3.3 测试
启动运行项目,访问接口/Test
效果如下:


对比之前默认容器可以发现,在两次的请求访问都一样,可以得到了 4个Transient实例,2个Scope实例,1个Singleton实例。
四、说明
下面主要针对Autofac中的注册组件、解析服务两大步骤,以及其中容器中对应实例的生命周期,进行说明。
4.1 注册组件
通过创建 ContainerBuilder 来注册组件,并且告诉容器哪些组件,暴露了哪些服务。
使用 Register() 方法来注册实现:
ContainerBuilder 包含一组 Register() 注册方法,而组件暴露服务,可用使用 ContainerBuilder 上的 As() 方法。
即在容器初始化时候,向容器组件添加对象的操作过程。
通过梳理Autofac所有可用的注册组件方法,显示如下图展示的流程图。

这里我们只说明下几种便捷的注册方法
4.1.1 反射注册
直接注册的组件必须是具体的类型,并可用暴露抽象和接口作为服务,但不能注册一个抽象和接口组件。
使用RegisterType<T>()或者RegisterType(typeof(T))方法:
在多个构造函数时,如果需要,也可手动指定一个构造函数。
使用 UsingConstructor 方法和构造方法中代表参数类型的类型。
4.1.2 实例注册
提前生成对象的实例并加入容器,以供注册组件时使用。
使用RegisterInstance()方法
如果单例中存在实例且需要在容器中被组件使用时,
4.1.3 Lambda表达式注册
当组件创建不再是简单调用构造方法时,可用利用lambda表达式来实现一些常规反射无法实现的操作。
比如一些复杂参数注册,参数注入,以及选择参数值实现等。
4.1.4 泛型注册
支持泛型注册操作,使用 RegisterGeneric() 方法:
4.1.5 条件注册
在一些特殊场景,可能需要通过加上判断条件,来决定是否执行该条注册语句。
两种方法:
- OnlyIf() - 提供一个表达式, 表示只有满足条件,才会执行语句。
- IfNotRegistered() - 表示没有其他服务注册的情况下,就执行语句。
方法在 ContainerBuilder.Build() 时执行并且以实际组件注册的顺序执行。
4.1.6 属性注入
构造方法参数注入是一种传值给组件的首选的方法。
在构造函数中是直接使用服务类型作为参数,然后AutoFac解析该类时,就会去容器内部已存在的组件中查找,然后将匹配的对象注入到构造函数中去。
但你同样也可以使用属性方法注入来传值。
是将容器内对应的组件直接注入到类内的属性中去,在注册该属性所属类的时候,需要使用PropertiesAutowired()方法额外标注。
这里不讨论属性注入的好坏,也不做说明服务层属性怎么注入,只讨论说明控制器中属性如何实现注入
- 注册组件方法,并使用属性注入PropertiesAutowired()标注。
- 在控制器中使用属性来接收, 其中注入属性必须标注为public
- 运行测试,发现如下

发现_transientService为null,所以根本没有注入成功。
这是因为控制器本身的实例(以及它的处理)是由框架创建和拥有的,而不是由容器所有
因此我们需要改变控制器本身的创建及其拥有。
- 在Startup.cs中修改ConfigureServices方法,替换从IServiceProvider中解析控制器实例的所有者。
注意,替换的方法一定要在AddControllers之前。
- 在ContainerBuilder中通过注册控制器,并使用属性注入功能实现.
这样就可以在Controller中进行属性注入了;
- 再次运行查看,发现已经成功注入了。

4.1.7 程序集注册
当我们需要实现批量注册的时候,也可以使用程序集的方式来注册,这也是常用的方法。
可通过指定过滤类型,服务,扫描模块等方式来找到需要注册的组件。
说明:
- RegisterAssemblyTypes() :接收包含一个或多个程序集的数组作为参数
- RegisterAssemblyModules() : 接收模块作为参数,进行模块扫描注册
- PublicOnly() :指定公有方法被注册
- Where() :要过滤注册的类型
- Except() :要排除的类型
- As() :反射出其实现的接口
- AsImplementedInterfaces() : 自动以其实现的所有接口类型暴露(包括IDisposable接口)
4.2 暴露服务
上面提到了注册组件时, 我们得告诉Autofac, 组件暴露了哪些服务。
在上面注册实现中,大部分使用到了As() 方法。
当然,Autofac也提供了其他标注来暴露服务的方法。
4.2.1 默认暴露自身类型服务
常用的几种方法如下:
4.2.2 多个暴露服务类型
以其实现的接口(interface)暴露服务,暴露的类型可以是多个,比如CallLogger类实现了ILogger接口和ICallInterceptor接口。
暴露服务后, 可以解析基于该服务的组件了. 但请注意, 一旦将组件暴露为一个特定的服务, 默认的服务 (组件类型) 将被覆盖。
所以,为了防止被其他服务覆盖,可以使用 AsSelf() 方法。
这样你既可以实现组件暴露一系列特定的服务, 又可以让它暴露默认的服务。
4.2.3 程序集注册指定暴露类型
- 可通过指定接口类型暴露服务,使用As() 方法
- 指定所有实现的接口类型进行暴露
使用AsImplementedInterfaces()函数实现,相当于一个类实现了几个接口(interface)就会暴露出几个服务,等价于上面连写多个As()的作用。
4.3 解析服务
在 注册完组件并暴露相应的服务后, 可以从创建的容器或其生命周期中解析服务。
使用 Resolve() 方法来解析实现:
通过梳理Autofac所有可用的解析服务方法,显示如下图展示的流程图。

在 注册完组件并暴露相应的服务后, 你可以从创建的容器或其子 生命周期 中解析服务. 让我们使用 Resolve() 方法来实现:
4.3.1 解析时传参
当解析服务时, 需要传参,可以使用Resolve() 方法来接受可变长度的参数。
- 可用参数类型
NamedParameter - 通过名称匹配目标参数
TypedParameter - 通过类型匹配目标参数 (需要匹配具体类型)
ResolvedParameter - 灵活的参数匹配
- 反射组件的参数
-
Lambda表达式组件的参数
-
不显式调用Resolve传参
4.3.2 隐式关系类型
这里不做详细说明,详见官方文档
4.4 生命周期

下面讲下AutoFac定义的几种生命周期作用域,并与.NET Core默认的生命周期作了简要的对比。
4.4.1 暂时性
每次在向服务容器进行请求时都会创建新的实例,相当于每次都new出一个。
注册方式:
使用InstancePerDependency()方法标注,如果不标注,这也是默认的选项。以下两种注册方法是等效的:
对比:
与默认的容器中自带的生命周期AddTransient相同,也是每次都是全新的实例。
使用AddTransient()注册:

4.4.2 作用域内
在每次Web请求时被创建一次实例,生命周期横贯整次请求。即在每个生命周期作用域内是单例的。
注册方式:
使用InstancePerLifetimeScope()方法标识:
对比
与默认的容器中自带的生命周期AddScoped相同,.NET Core框架自带的容器全权接管了请求和生命周期作用域的创建,使用Scoped()可以实现相同的效果。
使用AddScoped()注册:

4.4.3 匹配作用域内
即每个匹配的生命周期作用域一个实例。
该类型其实是上面的“作用域内”的其中一种,可以对实例的共享有更加精准的控制.。我们通过允许给域“打标签”,只要在这个特定的标签域内就是单例的。
- 注册
使用InstancePerMatchingLifetimeScope(string tagName)方法注册:
当你开始一个生命周期时, 提供的标签值和它就关联起来了。
- 解析
