原本计划是五篇文章的,每章发个半小时随便翻翻就能懂,可是第一篇发了以后,我发现.NET环境下不少人对IoC和DI都很排斥,搞得评论区异常热闹。git
同一个东西,在Java下和在.NET下能有这么大的差别,也是挺有意思的一件事情。github
因此我就把剩下四篇内容精简再精简,合成一篇了,权当是写给本身的一个备忘记录了。web
GitHub源码地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac数据库
源码是一个虚构的项目框架,相似于样例性质的代码或者测试程序,里面不少注释,对理解DI,或怎么在MVC、WebApi和Core Api分别实现依赖注入有很好的帮助效果。
因此,如下内容,配合源码食用效果更佳~api
老规矩,理论先行。mvc
一串声明了它所提供服务和它所消费依赖的代码。app
能够理解为容器内的基本单元,一个容器内会被注册不少个组件,每一个组件都有本身的信息:好比暴露的服务类型、生命周期域、绑定的具象对象等。框架
一个在提供和消费组件之间明肯定义的行为约定。
和项目中的xxxService不一样,AutoFac的服务是对容器而言的,能够简单的理解为上一章讲的组件的暴露类型
(即对外开放的服务类型),也就是As方法里的东西:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>();
这里,针对同一个注册对象(CallLogger),容器就对外暴露了两个服务(service),ILogger服务和ICallInterceptor服务。
指服务实例在你的应用中存在的时长:从开始实例化到最后释放结束。
指它在应用中能共享给其余组件并被消费的做用域。例如, 应用中有个全局的静态单例,那么该全局对象实例的 "做用域" 将会是整个应用。
实际上是把这两个概念组合在了一块儿, 能够理解为应用中的一个工做单元。后面详细讲。
容器是一个自动售货机
,组件是放在里面的在售商品
,服务是商品的出售名称
。
把商品(项目里的具象对象)放入自动售货机(容器)上架的过程叫注册
;
注册的时候会给商品贴上标签,标注该商品的名称,这个名称就叫服务
;
咱们还能够标注这个商品的适用人群和过时时间等(生命周期做用域
);
把这个包装后的商品放入自动售货机后,它就变成了在售商品(组件
)。
当有顾客须要某个商品时,他只要对着售货机报一个商品名(服务
名),自动售货机找到对应商品,抛出给客户,这个抛给你的过程,就叫作注入
你;
并且这个售货机比较智能,抛出前还能够先判断商品是否是过时了,该不应抛给你。
即在容器初始化时,向容器内添加对象的操做。AutoFac封装了如下几种便捷的注册方法:
直接指定注入对象与暴露类型,使用RegisterType<T>()
或者RegisterType(typeof(T))
方法:
builder.RegisterType<StudentRepository>() .As<IStudentRepository>(); builder.RegisterType(typeof(StudentService)) .As(typeof(IStudentService));
将实例注册到容器,使用RegisterInstance()
方法,一般有两种:
var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>();
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
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();
经过加上判断条件,来决定是否执行该条注册语句。
表示:若是没注册过xxx,就执行语句:
builder.RegisterType<TeacherRepository>() .AsSelf() .IfNotRegistered(typeof(ITeacherRepository));
只有当ITeacherRepository服务类型没有被注册过,才会执行该条注册语句。
表示:只有...,才会执行语句:
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));
使用As()
方法标识,暴露的类型能够是多个,好比CallLogger类实现了ILogger接口和ICallInterceptor接口,那么能够这么写:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>() .AsSelf();
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])//反射出其实现的接口,并指定以其实现的第一个接口类型暴露 }
使用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定义的几种生命周期做用域,上一篇评论里也有人提了,关于生命周期做用域这块确实不是很好理解,因此下面每中类型我都写了一个例子程序,这些例子程序对理解颇有帮助,只要能读懂这些例子程序,就必定能弄懂这些生命周期做用域。(例子项目源码里都有,能够去试着实际运行下,更易理解)
也叫每一个依赖一个实例。
即每次从容器里拿出来的都是全新对象,至关于每次都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指向不一样的两块内存,彼此之间没有关系。
打印结果:
即全局只有一个实例,在根容器和全部嵌套做用域内,每次解析返回的都是同一个实例。
使用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}"); }
上面的stu一、stu二、stu3都是同一个实例,在内存上它们指向同一个内存块。
打印结果:
即在每一个生命周期域内是单例的。
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:
即每一个匹配的
生命周期做用域一个实例。
该域类型实际上是上面的“域内单例”的其中一种,不同的是它容许咱们给域“打标签”,只要在这个特定的标签域内就是单例的。
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标签域内,因此这里解析不到,报异常 }
打印结果:
须要注意:
该种类型适用于“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,也只须要一个数据库实例,这样即能减小数据库实例初始化的消耗,还能实现事务的功能。
相比于AutoFac的丰富复杂,.NET Core就比较简单粗暴了,只要3种类型:
与AutoFac的瞬时实例(Instance Per Dependency)相同,每次都是全新的实例。
使用AddTransient()
注册:
services.AddTransient<IStudentService, StudentService>();
其意义与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>();
与AutoFac的单例(Single Instance)相同。
使用AddSingleton();
注册:
services.AddSingleton<StudentEntity>();
思路很简单,三步走:
新建AutoFac容器
初始化容器,向容器注册全部须要的依赖对象
将AutoFac解析器设置为系统的依赖解析器(Dependency Resolver)
除了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 { //内部实现 //...... }
启动时初始化容器,并把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); } } }
和MVC同样,思路很简单,三步走:
新建AutoFac容器
初始化容器,向容器注册全部须要的依赖对象
将AutoFac解析器设置为系统的依赖解析器(Dependency Resolver)
除了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的系统依赖解析器就能够了。
在项目启动时初始化容器:
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 Framework不一样,.NET Core把DI提到了很是重要的位置,其框架自己就集成了一套DI容器。
针对其自带DI,主要理解两个对象,IServiceCollection和 IServiceProvider。
用于向容器注册服务,能够和AutoFac的ContainerBuilder(容器构建器)类比。
负责从容器中向外部提供实例,能够和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容器:
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>(); } }
仍是上面的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>(); } }
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); } } }