【半小时大话.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项目
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> /// 初始化容器 /// </summary> /// <param name="func"></param> /// <returns></returns> public static void init(func<containerbuilder, containerbuilder> func = null) { //新建容器构建器,用于注册组件和服务 var builder = new containerbuilder(); //注册组件 mybuild(builder); func?.invoke(builder); //利用构建器创建容器 instance = builder.build(); //将autofac设置为系统di解析器 system.web.mvc.dependencyresolver.setresolver(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); } } }
项目主程序:
- global.asax启动项
启动时初始化容器:
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); //初始化容器 mvccontainer.init(); } } }
- 学生控制器:
直接利用构造注入就可以了:
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项目
api容器
除了autofac主包之外,还需要nuget导入autofac.api2包:
容器代码:
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> /// 初始化容器 /// </summary> /// <param name="config"></param> /// <param name="func"></param> public static void init(system.web.http.httpconfiguration config,func<containerbuilder, containerbuilder> func = null) { //新建容器构建器,用于注册组件和服务 var builder = new containerbuilder(); //注册组件 mybuild(builder); func?.invoke(builder); //利用构建器创建容器 instance = builder.build(); //将autofac解析器设置为系统解析器 config.dependencyresolver = 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); } } }
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); //获取httpconfiguration httpconfiguration config = globalconfiguration.configuration; //传入初始化容器 apicontainer.init(config); } } }
- 学生控制器:
直接利用构造函数注入即可:
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); } } }
- 调用
上一篇: 炸杏鲍菇的做法,赶紧学起来
下一篇: 好吃的汤米粉有这么多,推荐你几种汤米粉