浅谈.Net Core中使用Autofac替换自带的DI容器
为什么叫 浅谈 呢?就是字面上的意思,讲得比较浅,又不是不能用(这样是不对的)!!!
Aufofac大家都不陌生了,说是.Net生态下最优秀的IOC框架那是一点都过分。用的人多了,使用教程也十分丰富,官网教程也比较详细(如果英文功底还不错的话)。
那我为什么还要写这样一篇博客呢,一是用作学习笔记,二就是闲的。
废话不多说,开始正文
项目创建
云创建一个.Net Core Api项目,然后再添加一个类库,大概就是下面这样的结构:

新建一个类库项目,分别添加一个接口文件与类文件:

就这样,我们的演示方案就搭建完成了,下面就到了演示阶段。
方案演示
原始方案
俗话说的好,没有对象 new 一个就对了:
1 [HttpGet]
2 public string Original()
3 {
4 IUserService userService = new UserService();
5 return userService.GetName("Original");
6 }
结果当然是没问题的:

.Net Core自带DI
微软给我们提供的 DI 解决方案。如果是小项目,需要注入的服务不多,简直无敌好用,缺点就是不能批量注入,下面我们来复习一下:
先在 Startup 里面的 ConfigureServices 方法内注入(默认且只能构造函数注入)
services.AddScoped<IUserService, UserService>();
然后在控制器中拿到刚才注入的服务:
1 public class DefaultController : ControllerBase
2 {
3 private readonly IUserService userService;
4 public DefaultController(IUserService _userService)
5 {
6 this.userService = _userService;
7 }16
17 [HttpGet]
18 public string CoreDI()
19 {
20 return userService.GetName("CoreDI");
21 }
22 }
很显然,一点问题都没有:

Autofac
注意事项说在前面:
在 .Net Core2 中一般是把 Startup 的 ConfigureServices 方法返回值类型改为IServiceProvider,然后通过构建Autofac容器并注入服务后返回。
在 .Net Core3.0之后,集成方式做了部分调整
下面演示的版本是.Net Core 3.1,也就是调整后的版本。
1、先引用 Autofac 的包,看看这下载次数

2、在 Program 中改用 Autofac 来实现依赖注入
1 public static IHostBuilder CreateHostBuilder(string[] args) =>
2 Host.CreateDefaultBuilder(args)
3 // 就是这句
4 .UseServiceProviderFactory(new AutofacServiceProviderFactory())
5 .ConfigureWebHostDefaults(webBuilder =>
6 {
7 webBuilder.UseStartup<Startup>();
8 });
3、添加我们自定义的 Autofac 注册类,并注册我们需要的服务(默认构造函数注入,支持属性注入)
1 public class AutofacModuleRegister : Autofac.Module
2 {
3 //重写Autofac管道Load方法,在这里注册注入
4 protected override void Load(ContainerBuilder builder)
5 {
6 builder.RegisterType<UserService>().As<IUserService>();
7 }
8 }
4、在 Startup 类中添加方法:ConfigureContainer,
public void ConfigureContainer(ContainerBuilder builder)
{
// 直接用Autofac注册我们自定义的
builder.RegisterModule(new AutofacModuleRegister());
}
5、大功告成,控制器内的方法甚至不用去改
1 public class DefaultController : ControllerBase
2 {
3 private readonly IUserService userService;
4 public DefaultController(IUserService _userService)
5 {
6 this.userService = _userService;
7 }
8
9 [HttpGet]
10 public string Autofac()
11 {
12 return userService.GetName("Autofac");
13 }
14 }

演示到这里就结束了,是不是感觉 Autofac 比自带的 DI 还要麻烦。其实不然,下面我们就来看看 Autofac 对比自带 DI 的一些特有特性。
不同的特性
批量注入
之前的项目我们有了用户 UserService,需求更新,加入了商品(ProductService),有了商品那又怎么能少得了订单(OrderService),那后面是不是还得有售后、物流、仓库、营销......
如果是.Net Core 自带的注入框架,那就只能不停的:
1 services.AddScoped<IProductService, ProductService>();
2 services.AddScoped<IOrderService, OrderService>();
3 ......
这时候,Autofac 的好处就体现出来了:批量注入。
我们先回到上面的:AutofacModuleRegister 类,加入下面这段代码:
1 // 服务项目程序集
2 Assembly service = Assembly.Load("XXX.Service");
3 // 服务接口项目程序集
4 Assembly iservice = Assembly.Load("XXX.IService");
5 builder.RegisterAssemblyTypes(service, iservice)
6 .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract)
7 .InstancePerLifetimeScope()
8 .AsImplementedInterfaces();
上面的代码就是批量注入 XXX.Service 与 XXX.IService 项目下的服务与接口。
注意:如果需要注入的服务没有 interfac ,那么 builder.RegisterAssemblyTypes 就只需要传一个程序集就OK了。如果服务与接口同在一个项目,那也是要传两个程序集的哦。
然后我们在控制器去通过构造函数获取注入的实例:
1 private readonly IUserService userService;
2 private readonly IProductService productService;
3
4 public DefaultController(IUserService _userService, IProductService _productService)
5 {
6 this.userService = _userService;
7 this.productService = _productService;
8 }
再对之前的 Autofac 接口添油加醋:
1 [HttpGet]
2 public string Autofac()
3 {
4 var name = userService.GetName("Autofac");
5 return productService.Buy(name, "批量注入");
6 }
结果自然是没有问题的,如果后续需要加入其它服务都不用再单独注入了,是不是优点就体现出来了。批量注入还有一些其它的玩法,比如筛选类名,筛选父类等。

属性注入
.Net Core 自带的 DI 框架与 Autofac 默认都是构造函数注入,官方建议也是构造函数注入。
但是有些同学可能就不喜欢构造函数注入,再加上有些场景确实不适合构造函数注入(比如基类实体),所以 Autofac 也支持属性注入,下面我们来看看使用方法,在之前批量注入的基础上,我们简单改造一下:
1 Assembly service = Assembly.Load("Autofac.Service");
2 Assembly iservice = Assembly.Load("Autofac.Service");
3 builder.RegisterAssemblyTypes(service, iservice)
4 .Where(t => t.FullName.EndsWith("Service") && !t.IsAbstract)
5 .InstancePerLifetimeScope()
6 .AsImplementedInterfaces()
7 .PropertiesAutowired(); // 属性注入
对比构造函数注入,属性注入就多追加了 PropertiesAutowired() 函数,控制器内修改:
1 public IUserService userService { get; set; }
2 public IProductService productService { get; set; }
注意:属性注入记得将属性的访问修饰符改为注册类可访问的修饰符,否则会注入失败。
下面我们来看看使用效果:

咦,怎么会空引用呢?原因大概就是 Controller 是由 Mvc 模块管理的,不在 IOC 容器内,所以在 Controller 中无法使用 Autofac 注入的实例。
那为什么构造函数注入的时候又可以呢?大概或许可能他们都是构造函数注入吧...
为什么是大概呢?因为我暂时也没有具体去深入研究到底是什么原因导致的,如果有一天,我想起来去研究了并且有结果了,我会在这里补上。
我们先解决上面的问题先,在 Startup 的 ConfigureServices 方法底部加入如下代码:
// 使用 ServiceBasedControllerActivator 替换 DefaultControllerActivator;
// Controller 默认是由 Mvc 模块管理的,不在 Ioc 容器中。替换之后,将放在 Ioc 容器中。
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
然后回到我们的 AutofacModuleRegister 注入 Controller:
builder.RegisterTypes(GetAssemblyTypes<Startup>(type => typeof(ControllerBase).IsAssignableFrom(type)))
.PropertiesAutowired();
这样处理完后,属性注入就Ok了。

存储并提取容器实例
我们在之前项目的基础上添加两个项目 Common 与 Entities,存放公共类与实体类。
我们需要在实体类里面使用到 Common 项目中的某个类,结构如下:
// 基类实体
public class BaseEntity
{
public Class1 common_Class1 { get; set; } public string CreateId { get; set; } public void Create()
{
this.CreateId = common_Class1.getCurrentUserId();
}
}
// 公共类
public class Class1
{
public string getCurrentUserId()
{
return Guid.NewGuid().ToString();
}
}
从上面的接口中我们可以看到,我需要将 Class1 通过属性注入到容器中:
builder.RegisterType<Class1>().PropertiesAutowired().InstancePerLifetimeScope();
我们先在 Controller 中看看效果:
public Class1 class1 { get; set; }
[HttpGet]
public string Autofac()
{
return class1.getCurrentUserId();
}
很显然结果是没问题的:

那我们再到 BaseEntity 中去试试看:


咦,又出现空引用,注入失败了。其实这个问题很明显,我们使用的是 new 来实例化的 BaseEntity对象,没有遵循容器实例使用规则,自然就无法使用容器中的实例了。
大家可以自己试一下,将 new 改为属性注入就没问题了,但是这种方案并不友好,下面要说的是另一种方案。
我们再新添加一个公共类:ContainerHelper,并声明一个属性用来存储容器的实例:
public static class ContainerHelper
{
public static ILifetimeScope ContainerBuilder { get; set; }
}
然后回到 Startup 中,在 Configure 方法的底部加入如下代码:
ContainerHelper.ContainerBuilder = app.ApplicationServices.CreateScope().ServiceProvider.GetAutofacRoot();
再回到实体类中去使用:
public void Create()
{
if (common_Class1 == null)
{
using (var scope = ContainerHelper.ContainerBuilder.BeginLifetimeScope())
{
common_Class1 = scope.Resolve<Class1>();
}
}
this.CreateId = common_Class1.getCurrentUserId();
}


Autofac 的替换方案暂时就写到这里了,后续如果有新的理解或心得会再做修改,浅谈嘛就真的是浅谈,有错误或补充的地方请大家不吝赐教。
源码这里就不提供了,大家有耐心的可以跟着手敲一遍,虽然对理解没啥作用,但能使记忆更深刻一点。
浅谈.Net Core中使用Autofac替换自带的DI容器的更多相关文章
- ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)
在上一章中主要和大家分享了在ASP.NET Core中如何使用Autofac替换自带DI进行构造函数的批量依赖注入,本章将和大家继续分享如何使之能够同时支持属性的批量依赖注入. 约定: 1.仓储层接口 ...
- ASP.NET Core Web 应用程序系列(二)- 在ASP.NET Core中使用Autofac替换自带DI进行批量依赖注入(MVC当中应用)
在上一章中主要和大家分享在MVC当中如何使用ASP.NET Core内置的DI进行批量依赖注入,本章将继续和大家分享在ASP.NET Core中如何使用Autofac替换自带DI进行批量依赖注入. P ...
- 浅谈关于QT中Webkit内核浏览器
关于QT中Webkit内核浏览器是本文要介绍的内容,主要是来学习QT中webkit中浏览器的使用.提起WebKit,大家自然而然地想到浏览器.作为浏览器内部的主要构件,WebKit的主要工作是渲染.给 ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
- NET Core 3.0 AutoFac替换内置DI的新姿势
原文:NET Core 3.0 AutoFac替换内置DI的新姿势 .NET Core 3.0 和 以往版本不同,替换AutoFac服务的方式有了一定的变化,在尝试着升级项目的时候出现了一些问题. 原 ...
- 浅谈.Net Core DependencyInjection源码探究
前言 相信使用过Asp.Net Core开发框架的人对自带的DI框架已经相当熟悉了,很多刚开始接触.Net Core的时候觉得不适应,主要就是因为Core默认集成它的原因.它是Asp.Net ...
- 浅谈C++11中的多线程(二)
摘要 本篇文章围绕以下几个问题展开: 进程和线程的区别 何为并发?C++中如何解决并发问题?C++中多线程的基本操作 浅谈C++11中的多线程(一) - 唯有自己强大 - 博客园 (cnblogs.c ...
- 转: 浅谈C/C++中的指针和数组(二)
转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组 ...
- 转:浅谈C/C++中的指针和数组(一)
再次读的时候实践了一下代码,结果和原文不一致 error C2372: 'p' : redefinition; different types of indirection 不同类型的间接寻址 /// ...
随机推荐
- Django(13)django时区问题
前言 我们都知道时区,标准时区是UTC时区,django默认使用的就是UTC时区,所以我们存储在数据库中的时间是UTC的时间,但是当我们做的网站只面向国内用户,或者只是提供内部平台使用,我们希望存储在 ...
- C++ primer plus读书笔记——第1章 预备知识
第1章 预备知识 1. Ritchie希望有一种语言能将低级语言的效率.硬件访问能力和高级语言的通用性.可移植性融合在一起,于是他在旧语言的基础上开发了C语言. 2. 在C++获得一定程度的成功后,S ...
- OO第1.2次作业·魔鬼的三角函数化简
多年以后,面对办公室的屏幕,我会回忆起开始肝第二周OO作业的那个遥远的下午.那时的程序是一个一两百行的符号求导,基类与接口在包里一字排开,工整的注释一望到底 谁能想到,接下来的十几个小时我要经历什么样 ...
- 在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
微信开发包括公众号.企业微信.微信小程序等方面的开发内容,需要对腾信的微信API接口进行封装:包括事件.菜单.订阅用户.多媒体文件.图文消息.消息群发.微信支付和企业红包.摇一摇设备.语义理解.微信小 ...
- 《Ray Tracing in One Weekend》阅读笔记 - 9、Metal(金属)
如果我们希望不同的物体使用不同的材料,则需要进行设计决策.我们可以使用具有许多参数的通用材料,而将不同的材料类型仅将其中一些参数归零.这不是一个坏方法.或者我们可以有一个抽象的材料类来封装行为.我是后 ...
- Nginx——Docker下安装部署
前言 Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务 . 一. 环境说明 docker: 18.09.9-ce nginx: 1.1 ...
- JQuery 使用教程
引言 JQuery 是一个 JavaScript 库,它极大地简化了 JavaScript 编程.JQuery 拥有丰富的选择器,可以非常方便的获取和操作 DOM 元素,而在 JQuery 中所有选择 ...
- 6.5 scp:远程文件复制
scp命令 用于在不同的主机之间复制文件,它采用SSH协议来保证复制的安全性.scp命令每次都是全量完整复制,因此效率不高,适合第一次复制时使用,增量复制建议使用rsync命令替代. scp ...
- 7.10-11 visudo、sudo
7.10 visudo:编辑 sudoers文件 visudo命令是专门用来编辑/etc/sudoers这个文件的,同时提供语法检查等功能./etc/sudoers文件是sudo命令的配置文件 ...
- 8.5-7 mkfs、dumpe2fs、resize2fs
8.5 mkfs:创建Linux文件系统 mkfs命令用于在指定的设备(或硬盘分区等)上创建格式化并创建文件系统,fdisk和parted等分区工具相当于建房的人,把房子(硬盘),分成几居室( ...