【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入
系列目录
前言
本来计划是五篇文章的,每章发个半小时随便翻翻就能懂,但是第一篇发了之后,我发现.NET环境下很多人对IoC和DI都很排斥,搞得评论区异常热闹。
同一个东西,在Java下和在.NET下能有这么大的差异,也是挺有意思的一件事情。
所以我就把剩下四篇内容精简再精简,合成一篇了,权当是写给自己的一个备忘记录了。
GitHub源码地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac
源码是一个虚构的项目框架,类似于样例性质的代码或者测试程序,里面很多注释,对理解DI,或怎么在MVC、WebApi和Core Api分别实现依赖注入有很好的帮助效果。
所以,以下内容,配合源码食用效果更佳~
第一部分:详解AutoFac用法
名词解释
老规矩,理论先行。
组件(Components)
一串声明了它所提供服务和它所消费依赖的代码。
可以理解为容器内的基本单元,一个容器内会被注册很多个组件,每个组件都有自己的信息:比如暴露的服务类型、生命周期域、绑定的具象对象等。
服务(Services)
一个在提供和消费组件之间明确定义的行为约定。
和项目中的xxxService不同,AutoFac的服务是对容器而言的,可以简单的理解为上一章讲的组件的暴露类型
(即对外开放的服务类型),也就是As方法里的东西:
builder.RegisterType<CallLogger>()
.As<ILogger>()
.As<ICallInterceptor>();
这里,针对同一个注册对象(CallLogger),容器就对外暴露了两个服务(service),ILogger服务和ICallInterceptor服务。
生命周期作用域(LifeTimeScope)
- 生命周期
指服务实例在你的应用中存在的时长:从开始实例化到最后释放结束。
- 作用域
指它在应用中能共享给其他组件并被消费的作用域。例如, 应用中有个全局的静态单例,那么该全局对象实例的 "作用域" 将会是整个应用。
- 生命周期作用域
其实是把这两个概念组合在了一起, 可以理解为应用中的一个工作单元。后面详细讲。
怎么理解它们的关系
容器是一个自动售货机
,组件是放在里面的在售商品
,服务是商品的出售名称
。
把商品(项目里的具象对象)放入自动售货机(容器)上架的过程叫注册
;
注册的时候会给商品贴上标签,标注该商品的名称,这个名称就叫服务
;
我们还可以标注这个商品的适用人群和过期时间等(生命周期作用域
);
把这个包装后的商品放入自动售货机后,它就变成了在售商品(组件
)。
当有顾客需要某个商品时,他只要对着售货机报一个商品名(服务
名),自动售货机找到对应商品,抛出给客户,这个抛给你的过程,就叫做注入
你;
而且这个售货机比较智能,抛出前还可以先判断商品是不是过期了,该不该抛给你。
注册组件
即在容器初始化时,向容器内添加对象的操作。AutoFac封装了以下几种便捷的注册方法:
反射注册
直接指定注入对象与暴露类型,使用RegisterType<T>()
或者RegisterType(typeof(T))
方法:
builder.RegisterType<StudentRepository>()
.As<IStudentRepository>();
builder.RegisterType(typeof(StudentService))
.As(typeof(IStudentService));
实例注册
将实例注册到容器,使用RegisterInstance()
方法,通常有两种:
- new出一个对象注册:
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();
- 注册项目已存在单例:
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
Lambda表达式注册
builder.Register(x => new StudentRepository())
.As<IStudentRepository>();
builder.Register(x => new StudentService(x.Resolve<IStudentRepository>()))
.As<IStudentService>();
利用拉姆达注册可以实现一些常规反射无法实现的操作,比如一些复杂参数注册。
泛型注册
最常见的就是泛型仓储的注册:
builder.RegisterGeneric(typeof(BaseRepository<>))
.As(typeof(IBaseRepository<>))
.InstancePerLifetimeScope();
条件注册
通过加上判断条件,来决定是否执行该条注册语句。
- IfNotRegistered
表示:如果没注册过xxx,就执行语句:
builder.RegisterType<TeacherRepository>()
.AsSelf()
.IfNotRegistered(typeof(ITeacherRepository));
只有当ITeacherRepository服务类型没有被注册过,才会执行该条注册语句。
- OnlyIf
表示:只有...,才会执行语句:
builder.RegisterType<TeacherService>()
.AsSelf()
.As<ITeacherService>()
.OnlyIf(x =>
x.IsRegistered(new TypedService(typeof(ITeacherRepository)))||
x.IsRegistered(new TypedService(typeof(TeacherRepository))));
只有当ITeacherRepository服务类型或者TeacherRepository服务类型被注册过,才会执行该条注册语句。
程序集批量注册
最常用,也最实用的一个注册方法,使用该方法最好要懂点反射
的知识。
/// <summary>
/// 通过反射程序集批量注册
/// </summary>
/// <param name="builder"></param>
public static void BuildContainerFunc8(ContainerBuilder builder)
{
Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();
builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)
.Where(cc =>cc.Name.EndsWith("Repository")|//筛选
cc.Name.EndsWith("Service"))
.PublicOnly()//只要public访问权限的
.Where(cc=>cc.IsClass)//只要class型(主要为了排除值和interface类型)
//.Except<TeacherRepository>()//排除某类型
//.As(x=>x.GetInterfaces()[0])//反射出其实现的接口,默认以第一个接口类型暴露
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
builder.RegisterGeneric(typeof(BaseRepository<>))
.As(typeof(IBaseRepository<>));
}
如上会批量注册项目中所有的Repository和Service。
属性注入
讲属性注入之前,要先看下构造注入。
- 构造注入
即解析的时候,利用构造函数注入,形式如下:
/// <summary>
/// 学生逻辑处理
/// </summary>
public class StudentService : IStudentService
{
private readonly IStudentRepository _studentRepository;
/// <summary>
/// 构造注入
/// </summary>
/// <param name="studentRepository"></param>
public StudentService(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
}
在构造函数的参数中直接写入服务类型,AutoFac解析该类时,就会去容器内部已存在的组件中查找,然后将匹配的对象注入到构造函数中去。
- 属性注入
属性注入与构造注入不同,是将容器内对应的组件直接注入到类内的属性中去,形式如下:
/// <summary>
/// 教师逻辑处理
/// </summary>
public class TeacherService : ITeacherService
{
/// <summary>
/// 用于属性注入
/// </summary>
public ITeacherRepository TeacherRepository { get; set; }
public string GetTeacherName(long id)
{
return TeacherRepository?.Get(111).Name;
}
}
要使用这种属性注入,在注册该属性所属类的时候,需要使用PropertiesAutowired()
方法额外标注,如下:
builder.RegisterType<TeacherService>().PropertiesAutowired();
这样,容器在解析并实例化TeacherService类时,便会将容器内的组件与类内的属性做映射,如果相同则自动将组件注入到类内属性种。
- 注意
属性注入争议性很大,很多人称这是一种_反模式_,事实也确实如此。
使用属性注入会让代码可读性变得极其复杂(而复杂难懂的代码一定不是好的代码,不管用的技术有多高大上)。
但是属性注入也不是一无是处,因为属性注入有一个特性:
在构造注入的时候,如果构造函数的参数中有一个对象在容器不存在,那么解析就会报错。
但是属性注入就不一样了,当容器内没有与该属性类型对应的组件时,这时解析不会报异常,只会让这个属性保持为空类型(null)。
利用这个特性,可以实现一些特殊的操作。
暴露服务
即上面提到的As<xxx>()
函数,AutoFac提供了以下三种标注暴露服务类型的方法:
以其自身类型暴露服务
使用AsSelf()
方法标识,表示以其自身类型暴露,也是当没有标注暴露服务的时候的默认选项。
如下四种写法是等效的:
builder.RegisterType<StudentService>();//不标注,默认以自身类型暴露服务
builder.RegisterType<StudentService>().AsSelf();
builder.RegisterType<StudentService>().As<StudentService>();
builder.RegisterType<StudentService>().As(typeof(StudentService));
以其实现的接口(interface)暴露服务
使用As()
方法标识,暴露的类型可以是多个,比如CallLogger类实现了ILogger接口和ICallInterceptor接口,那么可以这么写:
builder.RegisterType<CallLogger>()
.As<ILogger>()
.As<ICallInterceptor>()
.AsSelf();
程序集批量注册时指定暴露类型
- 方法1:自己指定
public static void BuildContainerFunc8(ContainerBuilder builder)
{
Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();
builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)
.Where(cc =>cc.Name.EndsWith("Repository")|//筛选
cc.Name.EndsWith("Service"))
.As(x=>x.GetInterfaces()[0])//反射出其实现的接口,并指定以其实现的第一个接口类型暴露
}
- 方法2:以其实现的所有接口类型暴露
使用AsImplementedInterfaces()
函数实现,相当于一个类实现了几个接口(interface)就会暴露出几个服务,等价于上面连写多个As()的作用。
public static void BuildContainerFunc8(ContainerBuilder builder)
{
Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies();
builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)
.Where(cc =>cc.Name.EndsWith("Repository")|//筛选
cc.Name.EndsWith("Service"))
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
}
生命周期作用域
相当于UnitWork(工作单元)的概念。下面罗列出了AutoFac与.NET Core的生命周期作用域,并作了简要的对比。
AutoFac的生命周期作用域
下面讲下AutoFac定义的几种生命周期作用域,上一篇评论里也有人提了,关于生命周期作用域这块确实不是很好理解,所以下面每中类型我都写了一个例子程序,这些例子程序对理解很有帮助,只要能读懂这些例子程序,就一定能弄懂这些生命周期作用域。(例子项目源码里都有,可以去试着实际运行下,更易理解)
瞬时单例(Instance Per Dependency)
也叫每个依赖一个实例。
即每次从容器里拿出来的都是全新对象,相当于每次都new出一个。
在其他容器中也被标识为 'Transient'(瞬时) 或 'Factory'(工厂)。
- 注册
使用InstancePerDependency()
方法标注,如果不标注,这也是默认的选项。以下两种注册方法是等效的:
//不指定,默认就是瞬时的
builder.RegisterType<Model.StudentEntity>();
//指定其生命周期域为瞬时
builder.RegisterType<Model.StudentEntity>().InstancePerDependency();
- 解析:
using (var scope = Container.Instance.BeginLifetimeScope())
{
var stu1 = scope.Resolve<Model.StudentEntity>();
Console.WriteLine($"第1次打印:{stu1.Name}");
stu1.Name = "张三";
Console.WriteLine($"第2次打印:{stu1.Name}");
var stu2 = scope.Resolve<Model.StudentEntity>();
Console.WriteLine($"第2次打印:{stu2.Name}");
}
上面解析了2次,有两个实例,stu1和stu2指向不同的两块内存,彼此之间没有关系。
打印结果:
全局单例(Single Instance)
即全局只有一个实例,在根容器和所有嵌套作用域内,每次解析返回的都是同一个实例。
- 注册
使用SingleInstance()
方法标识:
builder.RegisterType<Model.StudentEntity>().SingleInstance();
- 解析:
//直接从根域内解析(单例下可以使用,其他不建议这样直接从根域内解析)
var stu1 = Container.Instance.Resolve<Model.StudentEntity>();
stu1.Name = "张三";
Console.WriteLine($"第1次打印:{stu1.Name}");
using (var scope1 = Container.Instance.BeginLifetimeScope())
{
var stu2 = scope1.Resolve<Model.StudentEntity>();
Console.WriteLine($"第2次打印:{stu2.Name}");
stu1.Name = "李四";
}
using (var scope2 = Container.Instance.BeginLifetimeScope())
{
var stu3 = scope2.Resolve<Model.StudentEntity>();
Console.WriteLine($"第3次打印:{stu3.Name}");
}
上面的stu1、stu2、stu3都是同一个实例,在内存上它们指向同一个内存块。
打印结果:
域内单例(Instance Per Lifetime Scope)
即在每个生命周期域内是单例的。
- 注册
使用InstancePerLifetimeScope()
方法标识:
x.RegisterType<Model.StudentEntity>().InstancePerLifetimeScope();
- 解析
//子域一
using (var scope1 = Container.Instance.BeginLifetimeScope())
{
var stu1 = scope1.Resolve<Model.StudentEntity>();
Console.WriteLine($"第1次打印:{stu1.Name}");
stu1.Name = "张三";
var stu2 = scope1.Resolve<Model.StudentEntity>();
Console.WriteLine($"第2次打印:{stu2.Name}");
}
//子域二
using (var scope2 = Container.Instance.BeginLifetimeScope())
{
var stuA = scope2.Resolve<Model.StudentEntity>();
Console.WriteLine($"第3次打印:{stuA.Name}");
stuA.Name = "李四";
var stuB = scope2.Resolve<Model.StudentEntity>();
Console.WriteLine($"第4次打印:{stuB.Name}");
}
如上,在子域一中,虽然解析了2次,但是2次解析出的都是同一个实例,即stu1和stu2指向同一个内存块Ⅰ。
子域二也一样,stuA和stuB指向同一个内存块Ⅱ,但是内存块Ⅰ和内存块Ⅱ却不是同一块。
打印结果如下,第1次和第3次为null:
匹配域内单例(Instance Per Matching Lifetime Scope)
即每个匹配的
生命周期作用域一个实例。
该域类型其实是上面的“域内单例”的其中一种,不一样的是它允许我们给域“打标签”,只要在这个特定的标签域内就是单例的。
- 注册
使用InstancePerMatchingLifetimeScope(string tagName)
方法注册:
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myTag");
- 解析
//myScope标签子域一
using (var myScope1 = Container.Instance.BeginLifetimeScope("myTag"))
{
var stu1 = myScope1.Resolve<Model.StudentEntity>();
stu1.Name = "张三";
Console.WriteLine($"第1次打印:{stu1.Name}");
var stu2 = myScope1.Resolve<Model.StudentEntity>();
Console.WriteLine($"第2次打印:{stu2.Name}");
//解析了2次,但2次都是同一个实例(stu1和stu2指向同一个内存块Ⅰ)
}
//myScope标签子域二
using (var myScope2 = Container.Instance.BeginLifetimeScope("myTag"))
{
var stuA = myScope2.Resolve<Model.StudentEntity>();
Console.WriteLine($"第3次打印:{stuA.Name}");
//因为标签域内已注册过,所以可以解析成功
//但是因为和上面不是同一个子域,所以解析出的实例stuA与之前的并不是同一个实例,指向另一个内存块Ⅱ
}
//无标签子域三
using (var noTagScope = Container.Instance.BeginLifetimeScope())
{
try
{
var stuOne = noTagScope.Resolve<Model.StudentEntity>();//会报异常
Console.WriteLine($"第4次正常打印:{stuOne.Name}");
}
catch (Exception e)
{
Console.WriteLine($"第4次异常打印:{e.Message}");
}
//因为StudentEntity只被注册到带有myScope标签域内,所以这里解析不到,报异常
}
打印结果:
需要注意:
- 第3次打印为null,不同子域即使标签相同,但也是不同子域,所以域之间不是同一个实例
- 在其他标签的域内(包括无标签域)解析,会报异常
每次请求内单例(Instance Per Request)
该种类型适用于“request”类型的应用,比如MVC和WebApi。
其实质其实又是上一种的“指定域内单例”的一种特殊情况:AutoFac内有一个静态字符串叫Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag
,其值为"AutofacWebRequest"
,当“指定域内单例”打的标签是这个字符串时,那它就是“每次请求内单例”了。
- 注册
使用InstancePerRequest()
方法标注:
builder.RegisterType<Model.StudentEntity>().InstancePerRequest();
也可以使用上面的域内单例的注册法(但是不建议):
//使用静态字符串标记
builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope(Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
//或者直接写明字符串
builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope("AutofacWebRequest");
这里用控制台程序不好举例子就不写解析代码了,要理解“每次请求内单例”的作用,最好的例子就是EF中的DBContext,我们在一次request请求内,即使是用到了多个Service和多个Repository,也只需要一个数据库实例,这样即能减少数据库实例初始化的消耗,还能实现事务的功能。
.NET Core的生命周期作用域(Service lifetimes)
相比于AutoFac的丰富复杂,.NET Core就比较简单粗暴了,只要3种类型:
瞬时实例(Transient)
与AutoFac的瞬时实例(Instance Per Dependency)相同,每次都是全新的实例。
使用AddTransient()
注册:
services.AddTransient<IStudentService, StudentService>();
请求内单例(Scoped)
其意义与AutoFac的请求内单例(Instance Per Request)相同,但实际如果真正在.NET Core中使用使用AutoFac的话,应该使用AutoFac的域内单例(Instance Per LifetimeScope)来代替。
原因是.NET Core框架自带的DI(Microsoft.Extensions.DependencyInjection
)全权接管了请求和生命周期作用域的创建,所以AutoFac无法控制,但是使用域内单例(Instance Per LifetimeScope)可以实现相同的效果。
使用AddScoped()
注册:
services.AddScoped<IStudentService, StudentService>();
单例(Singleton)
与AutoFac的单例(Single Instance)相同。
使用AddSingleton();
注册:
services.AddSingleton<StudentEntity>();
第二部分:.NET Framework Web程序AutoFac注入
MVC项目
思路很简单,三步走:
-
新建AutoFac容器
-
初始化容器,向容器注册所有需要的依赖对象
-
将AutoFac解析器设置为系统的依赖解析器(Dependency Resolver)
MVC容器
除了AutoFac主包之外,还需要Nuget导入AutoFac.Mvc5包:
容器代码:
using System;
using System.Linq;
using System.Reflection;
//
using Autofac;
using Autofac.Integration.Mvc;
//
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc
{
/// <summary>
/// .net framework MVC程序容器
/// </summary>
public static class MvcContainer
{
public static IContainer Instance;
/// <summary>
/// 初始化MVC容器
/// </summary>
/// <param name="func"></param>
/// <returns></returns>
public static System.Web.Mvc.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null)
{
//新建容器构建器,用于注册组件和服务
var builder = new ContainerBuilder();
//注册组件
MyBuild(builder);
func?.Invoke(builder);
//利用构建器创建容器
Instance = builder.Build();
//返回针对MVC的AutoFac解析器
return new AutofacDependencyResolver(Instance);
}
public static void MyBuild(ContainerBuilder builder)
{
Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();
//注册仓储 && Service
builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)
.Where(cc => cc.Name.EndsWith("Repository") |//筛选
cc.Name.EndsWith("Service"))
.PublicOnly()//只要public访问权限的
.Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型)
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
//注册泛型仓储
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));
//注册Controller
//方法1:自己根据反射注册
//builder.RegisterAssemblyTypes(assemblies)
// .Where(cc => cc.Name.EndsWith("Controller"))
// .AsSelf();
//方法2:用AutoFac提供的专门用于注册MvcController的扩展方法
Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc"));
builder.RegisterControllers(mvcAssembly);
}
}
}
这里Init()
初始化函数返回类型变成了System.Web.Mvc.IDependencyResolver
接口,即MVC的系统依赖解析器。
AutoFac自己封装了一个AutofacDependencyResolver
类(AutoFac依赖解析器类)实现了这个接口,所以直接new一个AutofacDependencyResolver类返回,等下把这个AutoFac依赖解析器类设置为MVC的系统依赖解析器就可以了。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Web.Mvc;
namespace Autofac.Integration.Mvc
{
/// <summary>
/// Autofac implementation of the <see cref="T:System.Web.Mvc.IDependencyResolver" /> interface.
/// </summary>
public class AutofacDependencyResolver : IDependencyResolver
{
//内部实现
//......
}
项目主程序:
- Global.asax启动项
启动时初始化容器,并把AutoFac生成的解析器设置为系统依赖解析器:
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;
namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//初始化容器,并返回适用于MVC的AutoFac解析器
System.Web.Mvc.IDependencyResolver autoFacResolver = MvcContainer.Init();
//将AutoFac解析器设置为系统DI解析器
DependencyResolver.SetResolver(autoFacResolver);
}
}
}
其中DependencyResolver.SetResolver()
为MVC封装的一个静态方法,用于设置MVC的依赖解析器。
其参数只要是实现了System.Web.Mvc.IDependencyResolver
接口的对象都可以,AutoFac自己封装的解析器AutofacDependencyResolver
类实现了这个接口,所以可以传进来,从而实现了让AutoFac接管MVC的依赖注入。
- 学生控制器:
直接利用构造注入就可以了:
using System.Web.Mvc;
//
using Ray.EssayNotes.AutoFac.Service.IService;
namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc.Controllers
{
/// <summary>
/// 学生Api
/// </summary>
public class StudentController : Controller
{
private readonly IStudentService _studentService;
/// <summary>
/// 构造注入
/// </summary>
/// <param name="studentService"></param>
public StudentController(IStudentService studentService)
{
_studentService = studentService;
}
/// <summary>
/// 获取学生姓名
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public string GetStuNameById(long id)
{
return _studentService.GetStuName(id);
}
}
}
运行调用Api
WebApi项目
和MVC一样,思路很简单,三步走:
-
新建AutoFac容器
-
初始化容器,向容器注册所有需要的依赖对象
-
将AutoFac解析器设置为系统的依赖解析器(Dependency Resolver)
Api容器
除了AutoFac主包之外,还需要Nuget导入AutoFac.WebApi2包:
容器代码:
using System;
using System.Linq;
using System.Reflection;
//
using Autofac;
using Autofac.Integration.WebApi;
//
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Repository.IRepository;
namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc
{
/// <summary>
/// .NET Framework WebApi容器
/// </summary>
public static class ApiContainer
{
public static IContainer Instance;
/// <summary>
/// 初始化Api容器
/// </summary>
/// <param name="func"></param>
public static System.Web.Http.Dependencies.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null)
{
//新建容器构建器,用于注册组件和服务
var builder = new ContainerBuilder();
//注册组件
MyBuild(builder);
func?.Invoke(builder);
//利用构建器创建容器
Instance = builder.Build();
//返回针对WebApi的AutoFac解析器
return new AutofacWebApiDependencyResolver(Instance);
}
public static void MyBuild(ContainerBuilder builder)
{
var assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();
//注册仓储 && Service
builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes)
.Where(cc => cc.Name.EndsWith("Repository") |//筛选
cc.Name.EndsWith("Service"))
.PublicOnly()//只要public访问权限的
.Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型)
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
//注册泛型仓储
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));
//注册ApiController
//方法1:自己根据反射注册
//Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".NetFrameworkApi")).ToArray();
//builder.RegisterAssemblyTypes(controllerAssemblies)
// .Where(cc => cc.Name.EndsWith("Controller"))
// .AsSelf();
//方法2:用AutoFac提供的专门用于注册ApiController的扩展方法
Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkApi"));
builder.RegisterApiControllers(mvcAssembly);
}
}
}
这里Init()
初始化函数返回类型变成了System.Web.Http.Dependencies.IDependencyResolver
接口,即WebApi的系统依赖解析器。
AutoFac自己封装了一个AutofacWebApiDependencyResolver
类(AutoFac针对WebApi的依赖解析器类)实现了这个接口,所以直接new一个AutofacWebApiDependencyResolver类返回,等下把这个AutoFac依赖解析器类设置为WebApi的系统依赖解析器就可以了。
WebApi主程序
- Global.asax启动项
在项目启动时初始化容器:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;
namespace Ray.EssayNotes.AutoFac.NetFrameworkApi
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//初始化容器,并返回适用于WebApi的AutoFac解析器
System.Web.Http.Dependencies.IDependencyResolver autoFacResolver = ApiContainer.Init();
//获取HttpConfiguration
HttpConfiguration config = GlobalConfiguration.Configuration;
//将AutoFac解析器设置为系统DI解析器
config.DependencyResolver = autoFacResolver;
}
}
}
这里跟上面的MVC项目不太一样,是通过HttpConfiguration对象来设置依赖解析器的,但是原理相同,不赘述了。
- 学生控制器:
直接利用构造函数注入即可:
using System.Web.Http;
//
using Ray.EssayNotes.AutoFac.Service.IService;
namespace Ray.EssayNotes.AutoFac.NetFrameworkApi.Controllers
{
/// <summary>
/// 学生Api
/// </summary>
public class StudentController : ApiController
{
private readonly IStudentService _studentService;
/// <summary>
/// 构造注入
/// </summary>
/// <param name="studentService"></param>
public StudentController(IStudentService studentService)
{
_studentService = studentService;
}
/// <summary>
/// 获取学生姓名
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
[Route("Student/GetStuNameById")]
public string GetStuNameById(long id)
{
return _studentService.GetStuName(123);
}
}
}
运行调用接口
第三部分:.NET Core的DI
自带的DI
与.NET Framework不同,.NET Core把DI提到了非常重要的位置,其框架本身就集成了一套DI容器。
针对其自带DI,主要理解两个对象,IServiceCollection和 IServiceProvider。
- IServiceCollection
用于向容器注册服务,可以和AutoFac的ContainerBuilder(容器构建器)类比。
- IServiceProvider
负责从容器中向外部提供实例,可以和AutoFac的解析的概念类比。
注册的地方就在主程序下的startup类中。
但是其本身的注册语法并没有AutoFac那么丰富,泛型注册、批量注册这些全都没有,只有下面这种最基础的一个一个注册的形式:
using System.Linq;
using System.Reflection;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions;
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//注册
//自定义注册
//注册仓储
services.AddScoped<ITeacherRepository, TeacherRepository>();
services.AddScoped<IStudentRepository, StudentRepository>();
services.AddScoped<IBaseRepository<StudentEntity>, BaseRepository<StudentEntity>>();
services.AddScoped<IBaseRepository<TeacherEntity>, BaseRepository<TeacherEntity>>();
services.AddScoped<IBaseRepository<BookEntity>, BaseRepository<BookEntity>>();
//注册Service
services.AddScoped<IStudentService, StudentService>();
services.AddScoped<ITeacherService, TeacherService>();
services.AddScoped<IBookService, BookService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
所以,大家通常都会自己去扩展这些注册方法,以实现一些和AutoFac一样的便捷的注册操作,下面我根据反射写了一个小扩展,写的比较简单潦草,可以参考下:
扩展
扩展代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Model;
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Service.IService;
using Ray.EssayNotes.AutoFac.Service.Service;
namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions
{
/// <summary>
/// asp.net core注册扩展
/// </summary>
public static class RegisterExtension
{
/// <summary>
/// 反射批量注册
/// </summary>
/// <param name="services"></param>
/// <param name="assembly"></param>
/// <param name="serviceLifetime"></param>
/// <returns></returns>
public static IServiceCollection AddAssemblyServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
var typeList = new List<Type>();//所有符合注册条件的类集合
//筛选当前程序集下符合条件的类
List<Type> types = assembly.GetTypes().
Where(t => t.IsClass && !t.IsGenericType)//排除了泛型类
.ToList();
typeList.AddRange(types);
if (!typeList.Any()) return services;
var typeDic = new Dictionary<Type, Type[]>(); //待注册集合<class,interface>
foreach (var type in typeList)
{
var interfaces = type.GetInterfaces(); //获取接口
typeDic.Add(type, interfaces);
}
//循环实现类
foreach (var instanceType in typeDic.Keys)
{
Type[] interfaceTypeList = typeDic[instanceType];
if (interfaceTypeList == null)//如果该类没有实现接口,则以其本身类型注册
{
services.AddServiceWithLifeScoped(null, instanceType, serviceLifetime);
}
else//如果该类有实现接口,则循环其实现的接口,一一配对注册
{
foreach (var interfaceType in interfaceTypeList)
{
services.AddServiceWithLifeScoped(interfaceType, instanceType, serviceLifetime);
}
}
}
return services;
}
/// <summary>
/// 暴露类型可空注册
/// (如果暴露类型为null,则自动以其本身类型注册)
/// </summary>
/// <param name="services"></param>
/// <param name="interfaceType"></param>
/// <param name="instanceType"></param>
/// <param name="serviceLifetime"></param>
private static void AddServiceWithLifeScoped(this IServiceCollection services, Type interfaceType, Type instanceType, ServiceLifetime serviceLifetime)
{
switch (serviceLifetime)
{
case ServiceLifetime.Scoped:
if (interfaceType == null) services.AddScoped(instanceType);
else services.AddScoped(interfaceType, instanceType);
break;
case ServiceLifetime.Singleton:
if (interfaceType == null) services.AddSingleton(instanceType);
else services.AddSingleton(interfaceType, instanceType);
break;
case ServiceLifetime.Transient:
if (interfaceType == null) services.AddTransient(instanceType);
else services.AddTransient(interfaceType, instanceType);
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, null);
}
}
}
}
利用这个扩展,我们在startup里就可以用类似AutoFac的语法来注册了。
主程序
注册代码:
using System.Linq;
using System.Reflection;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions;
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//注册
//自定义批量注册
Assembly[] assemblies = ReflectionHelper.GetAllAssembliesCoreWeb();
//注册repository
Assembly repositoryAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Repository"));
services.AddAssemblyServices(repositoryAssemblies);
//注册service
Assembly serviceAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Service"));
services.AddAssemblyServices(serviceAssemblies);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
其实AutoFac针对.NET Core已经帮我们集成了一套注册的扩展,我们可以通过两种方式把AutoFac引入.NET Core:一种是将AutoFac容器作为辅助容器,与.NET Core的DI共存,我们可以同时向两个容器里注册组件;一种是让AutoFac容器接管.NET Core的DI,注册时只选择往Autofac容器中注册。
下面就分别实现下这两种引入AutoFac的方式。
AutoFac作为辅助注册
Core容器
先按照之前写AutoFac容器的方法,新建一个针对Core的AutoFac容器:
using System;
//
using Microsoft.Extensions.DependencyInjection;
//
using Autofac;
using Autofac.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc
{
/// <summary>
/// Core的AutoFac容器
/// </summary>
public static class CoreContainer
{
/// <summary>
/// 容器实例
/// </summary>
public static IContainer Instance;
/// <summary>
/// 初始化容器
/// </summary>
/// <param name="services"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IServiceProvider Init(IServiceCollection services, Func<ContainerBuilder, ContainerBuilder> func = null)
{
//新建容器构建器,用于注册组件和服务
var builder = new ContainerBuilder();
//将Core自带DI容器内的服务迁移到AutoFac容器
builder.Populate(services);
//自定义注册组件
MyBuild(builder);
func?.Invoke(builder);
//利用构建器创建容器
Instance = builder.Build();
return new AutofacServiceProvider(Instance);
}
/// <summary>
/// 自定义注册
/// </summary>
/// <param name="builder"></param>
public static void MyBuild(this ContainerBuilder builder)
{
var assemblies = Helpers.ReflectionHelper.GetAllAssembliesCoreWeb();
//注册仓储 && Service
builder.RegisterAssemblyTypes(assemblies)
.Where(cc => cc.Name.EndsWith("Repository") |//筛选
cc.Name.EndsWith("Service"))
.PublicOnly()//只要public访问权限的
.Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型)
.AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口)
//注册泛型仓储
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));
/*
//注册Controller
Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".CoreApi")).ToArray();
builder.RegisterAssemblyTypes(controllerAssemblies)
.Where(cc => cc.Name.EndsWith("Controller"))
.AsSelf();
*/
}
}
}
主程序
在主程序中新建一个StartupWithAutoFac类,用于注册。
StartupWithAutoFac代码:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Autofac;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class StartupWithAutoFac
{
public IConfiguration Configuration { get; }
public StartupWithAutoFac(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
/// <summary>
/// 利用该方法可以使用AutoFac辅助注册,该方法在ConfigureServices()之后执行,所以当发生覆盖注册时,以后者为准。
/// 不要再利用构建器去创建AutoFac容器了,系统已经接管了。
/// </summary>
/// <param name="builder"></param>
public void ConfigureContainer(ContainerBuilder builder)
{
builder.MyBuild();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
这里其他地方与原startup都相同,只是多了一个ConfigureContainer()方法,在该方法内可以按照AutoFac的语法进行自由注册。
然后修改program类,将AutoFac hook进管道,并将StartupWithAutoFac类指定为注册入口:
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
//第一种:使用自带DI
//.UseStartup<Startup>();
//第二种:添加AutoFac作为辅助容器
.HookAutoFacIntoPipeline()
.UseStartup<StartupWithAutoFac>();
//第三种:添加AutoFac接管依赖注入
//.UseStartup<StartupOnlyAutoFac>();
}
}
AutoFac接管注册
容器
还是上面的CoreContainer容器。
主程序
主程序新建一个StartupOnlyAutoFac类,
代码如下:
using System;
//
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//
using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class StartupOnlyAutoFac
{
public IConfiguration Configuration { get; }
public StartupOnlyAutoFac(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
return CoreContainer.Init(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
这里直接改了ConfigureServices()
方法的返回类型,然后在该方法内直接利用AutoFac注册。
最后当然也要更改下program类,指定StartupOnlyAutoFac类为注册入口。
代码:
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace Ray.EssayNotes.AutoFac.CoreApi
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
//第一种:使用自带DI
//.UseStartup<Startup>();
//第二种:添加AutoFac作为辅助容器
//.HookAutoFacIntoPipeline()
//.UseStartup<StartupWithAutoFac>();
//第三种:添加AutoFac接管依赖注入
.UseStartup<StartupOnlyAutoFac>();
}
}
运行调用
- StudentController
using Microsoft.AspNetCore.Mvc;
//
using Ray.EssayNotes.AutoFac.Service.IService;
namespace Ray.EssayNotes.AutoFac.CoreApi.Controllers
{
[ApiController]
public class StudentController : ControllerBase
{
private readonly IStudentService _studentService;
public StudentController(IStudentService studentService)
{
_studentService = studentService;
}
[Route("Student/GetStuNameById")]
public string GetStuNameById(long id)
{
return _studentService.GetStuName(id);
}
}
}
- 调用