前言

看到标题可能大家会有所疑问controller和ioc能有啥羁绊,但是我还是拒绝当一个标题党的。相信有很大一部分人已经知道了这么一个结论,默认情况下asp.net core的controller并不会托管到ioc容器中,注意关键字我说的是”默认”,首先咱们不先说为什么,如果还有不知道这个结论的同学们可以自己验证一下,验证方式也很简单,大概可以通过以下几种方式。

验证controller不在ioc中

首先,我们可以尝试在serviceprovider中获取某个controller实例,比如

这是最直接的方式,可以在ioc容器中获取注册过的类型实例,很显然结果会为null。另一种方式,也是利用它的另一个特征,那就是通过构造注入的方式,如下所示我们在ordercontroller中注入productcontroller,显然这种方式是不合理的,但是为了求证一个结果,我们这里仅做演示,强烈不建议实际开发中这么写,这是不规范也是不合理的写法

结果显然是会报一个错invalidoperationexception: unable to resolve service for type ‘productcontroller’ while attempting to activate ‘ordercontroller’。原因就是因为productcontroller并不在ioc容器中,所以通过注入的方式会报错。还有一种方式,可能不太常用,这个是利用注入的一个特征,可能有些同学已经了解过了,那就是通过自带的di,即使一个类中包含多个构造函数,它也会选择最优的一个,也就是说自带的di允许类包含多个构造函数。利用这个特征,我们可以在controller中验证一下

我们在controller中编写了两个构造函数,理论上来说这是符合di特征的,运行起来测试一下,依然会报错invalidoperationexception: multiple constructors accepting all given argument types have been found in type ‘ordercontroller’. there should only be one applicable constructor。以上种种都是为了证实一个结论,默认情况下controller并不会托管到ioc当中。

defaultcontrollerfactory源码探究

上面虽然我们看到了一些现象,能说明controller默认情况下并不在ioc中托管,但是还没有足够的说服力,接下来我们就来查看源码,这是最有说服力的。我们找到controller工厂注册的地方,在mvccoreservicecollectionextensions扩展类中[点击查看源码]的addmvccoreservices方法里

由此我们可以得出,默认的controller创建工厂类为defaultcontrollerfactory,那么我们直接找到源码位置[点击查看源码],
为了方便阅读,精简一下源码如下所示

用过上面的源码可知,真正创建controller的地方在_controlleractivator.create方法中,通过上面的源码可知为icontrolleractivator默认注册的是defaultcontrolleractivator类,直接找到源码位置[点击查看源码],我们继续简化一下源码如下所示

通过上面的代码我们依然要继续深入到itypeactivatorcache实现中去寻找答案,通过查看mvccoreservicecollectionextensions类的addmvccoreservices方法源码我们可以找到如下信息

有了这个信息,我们可以直接找到typeactivatorcache类的源码[点击查看源码]代码并不多,大致如下所示

通过上面类的代码我们可以清晰的得出一个结论,默认情况下controller实例是由objectfactory创建出来的,而objectfactory实例是由activatorutilities的createfactory创建出来,所以controller实例每次都是由objectfactory创建而来,并非注册到ioc容器中。并且我们还可以得到一个结论objectfactory应该是一个委托,我们找到objectfactory定义的地方[点击查看源码]

这个确实如我们猜想的那般,这个委托会通过iserviceprovider实例去构建类型的实例,通过上述源码相关的描述我们会产生一个疑问,既然controller实例并非由ioc容器托管,它由objectfactory创建而来,但是objectfactory实例又是由activatorutilities构建的,那么生产对象的核心也就在activatorutilities类中,接下来我们就来探究一下activatorutilities的神秘面纱。

activatorutilities类的探究

书接上面,我们知道了activatorutilities类是创建controller实例最底层的地方,那么activatorutilities到底和容器是啥关系,因为我们看到了activatorutilities创建实例需要依赖serviceprovider,一切都要从找到activatorutilities类的源码开始。我们最初接触这个类的地方在于它通过createfactory方法创建了objectfactory实例,那么我们就从这个地方开始,找到源码位置[点击查看源码]实现如下

activatorutilities类的createfactory方法代码虽然比较简单,但是它涉及到调用了其他方法,由于嵌套的比较深代码比较多,而且不是本文讲述的重点,我们就不再这里细说了,我们可以大概的描述一下它的工作流程。

  • 首先在给定的类型里查找到合适的构造函数,这里我们可以理解为查找controller的构造函数。
  • 然后得到构造信息,并得到构造函数的参数与给定类型参数的对应关系
  • 通过构造信息和构造参数的对应关系,在iserviceprovider得到对应类型的实例为构造函数赋值
  • 最后经过上面的操作通过初始化指定的构造函数来创建给定controller类型的实例

综上述的相关步骤,我们可以得到一个结论,controller实例的初始化是通过遍历controller类型构造函数里的参数,然后根据构造函数每个参数的类型在iserviceprovider查找已经注册到容器中相关的类型实例,最终初始化得到的controller实例。这就是在iserviceprovider得到需要的依赖关系,然后创建自己的实例,它内部是使用的表达式树来完成的这一切,可以理解为更高效的反射方式。

关于activatorutilities类还包含了其他比较实用的方法,比如createinstance方法

它可以通过构造注入的方式创建指定类型t的实例,其中构造函数里具体的参数实例是通过在iserviceprovider实例里获取到的,比如我们我们有这么一个类

其中它所依赖的iorderservice和ipersonservice实例是注册到ioc容器中的

然后你想获取到ordercontroller的实例,但是它只包含一个有参构造函数,但是构造函数的参数都以注册到ioc容器中。当存在这种场景你便可以通过以下方式得到你想要的类型实例,如下所示

即使你的类型ordercontroller并没有注册到ioc容器中,但是它的依赖都在容器中,你也可以通过构造注入的方式得到你想要的实例。总的来说activatorutilities里的方法还是比较实用的,有兴趣的同学可以自行尝试一下,也可以通过查看activatorutilities源码的方式了解它的工作原理。

addcontrollersasservices方法

上面我们主要是讲解了默认情况下controller并不是托管到ioc容器中的,它只是表现出来的让你以为它是在ioc容器中,因为它可以通过构造函数注入相关实例,这主要是activatorutilities类的功劳。说了这么多controller实例到底可不可以注册到ioc容器中,让它成为真正受到ioc容器的托管者。要解决这个,必须要满足两点条件

  • 首先,需要将controller注册到ioc容器中,但是仅仅这样还不够,因为controller是由controllerfactory创建而来
  • 其次,我们要改造controllerfactory类中创建controller实例的地方让它从容器中获取controller实例,这样就解决了所有的问题

如果我们自己去实现将controller托管到ioc容器中,就需要满足以上两个操作一个是要将controller放入容器,然后让创建controller的地方从ioc容器中直接获取controller实例。庆幸的是,微软帮我们封装了一个相关的方法,它可以帮我们解决将controller托管到ioc容器的问题,它的使用方法如下所示

相信大家都看到了,玄机就在addcontrollersasservices方法中,但是它存在于mvccoremvcbuilderextensions类和mvccoremvccorebuilderextensions类中,不过问题不大,因为它们的代码是完全一样的。只是因为你可以通过多种方式构建web项目比如addmvc或者addmvccore,废话不多说直接上代码[点击查看源码]

第一点没问题那就是将controller实例添加到ioc容器中,第二点它替换掉了defaultcontrolleractivator为为servicebasedcontrolleractivator。通过上面我们讲述的源码了解到defaultcontrolleractivator是默认提供controller实例的地方是获取controller实例的核心所在,那么我们看看servicebasedcontrolleractivator与defaultcontrolleractivator到底有何不同,直接贴出代码[点击查看源码]

相信大家对上面的代码一目了然了,和我们上面描述的一样,将创建controller实例的地方改造了在容器中获取的方式。不知道大家有没有注意到servicebasedcontrolleractivator的release的方法居然没有实现,这并不是我没有粘贴出来,确实是没有代码,之前我们看到的defaultcontrolleractivator可是有调用controller的disposed的方法,这里却啥也没有。相信聪明的你已经想到了,因为controller已经托管到了ioc容器中,所以他的生命及其相关释放都是由ioc容器完成的,所以这里不需要任何操作。

我们上面还看到了注册controller实例的时候使用的是tryaddtransient方法,也就是说每次都会创建controller实例,至于为什么,我想大概是因为每次请求都其实只会需要一个controller实例,况且efcore的注册方式官方建议也是scope的,而这里的scope正是对应的一次controller请求。在加上自带的ioc会提升依赖类型的声明周期,如果将controller注册为单例的话如果使用了efcore那么它也会被提升为单例,这样会存在很大的问题。也许正是基于这个原因默认才将controller注册为transient类型的,当然这并不代表只能注册为transient类型的,如果你不使用类似efcore这种需要作用域为scope的服务的时候,而且保证使用的主键都可以使用单例的话,完全可以将controller注册为别的生命周期,当然这种方式个人不是很建议。

controller结合autofac

有时候大家可能会结合autofac一起使用,autofac确实是一款非常优秀的ioc框架,它它支持属性和构造两种方式注入,关于autofac托管自带ioc的原理咱们在之前的文章浅谈.net core dependencyinjection源码探究中曾详细的讲解过,这里咱们就不过多的描述了,咱们今天要说的是autofac和controller的结合。如果你想保持和原有的ioc一致的使用习惯,即只使用构造注入的话,你只需要完成两步即可

首先将默认的ioc容器替换为autofac,具体操作也非常简单,如下所示

然后就是咱们之前说的,要将controller放入容器中,然后修改生产controller实例的controllerfactory的操作为在容器中获取,当然这一步微软已经为我们封装了便捷的方法

只需要通过上面简单得两步,既可以将controller托管到autofac容器中。但是,我们说过了autofac还支持属性注入,但是默认的方式只支持构造注入的方式,那么怎么让controller支持属性注入呢?我们还得从最根本的出发,那就是解决controller实例存和取的问题

首先为了让controller托管到autofac中并且支持属性注入,那么就只能使用autofac的方式去注册controller实例,具体操作是在startup类中添加configurecontainer方法,然后注册controller并声明支持属性注入

其次是解决取的问题,这里我们就不需要addcontrollersasservices方法了,因为addcontrollersasservices解决了controller实例在ioc中存和取的问题,但是这里我们只需要解决controller取得问题说只需要使用servicebasedcontrolleractivator即可,具体操作是

仅需要在默认的状态下完成这两步,既可以解决controller托管到autofac中并支持属性注入的问题,这也是最合理的方式。当然如果你使用addcontrollersasservices可是可以实现相同的效果了,只不过是没必要将容器重复的放入容器中了。

总结

本文我们讲述了关于asp.net core controller与ioc结合的问题,我觉得这是有必要让每个人都有所了解的知识点,因为在日常的web开发中controller太常用了,知道这个问题可能会让大家在开发中少走一点弯路,接下来我们来总结一下本文大致讲解的内容

  1. 首先说明了一个现象,那就是默认情况下controller并不在ioc容器中,我们也通过几个示例验证了一下。
  2. 其次讲解了默认情况下创造controller实例真正的类activatorutilities,并大致讲解了activatorutilities的用途。
  3. 然后我们找到了将controller托管到ioc容器中的办法addcontrollersasservices,并探究了它的源码,了解了它的工作方式。
  4. 最后我们又演示了如何使用最合理的方式将controller结合autofac一起使用,并且支持属性注入。

到此这篇关于asp.net core controller与ioc结合问题整理的文章就介绍到这了,更多相关asp.net core controller与ioc结合问题内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!