系列目录

  1. 第一章|理论基础+实战控制台程序实现autofac注入

  2. 第二章|autofac的使用技巧

  3. 第三章|实战asp.net framework web程序实现autofac注入

  4. 第四章|实战asp.net core自带di实现依赖注入

  5. 第五章|实战asp.net core引入autofac的两种方式

前言

本来计划是五篇文章的,但是第一篇发了之后,我发现.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);
        }
    }
}
  • 调用