您现在的位置是:网站首页> 编程资料编程资料

ASP.NET Core Controller与IOC结合问题整理_实用技巧_

2023-05-24 351人已围观

简介 ASP.NET Core Controller与IOC结合问题整理_实用技巧_

前言

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

验证Controller不在IOC中

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

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var productController = app.ApplicationServices.GetService(); }

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

 public class OrderController : Controller { private readonly ProductController _productController; public OrderController(ProductController productController) { _productController = productController; } public IActionResult Index() { return View(); } }

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

 public class OrderController : Controller { private readonly IOrderService _orderService; private readonly IPersonService _personService; public OrderController(IOrderService orderService) { _orderService = orderService; } public OrderController(IOrderService orderService, IPersonService personService) { _orderService = orderService; _personService = personService; } public IActionResult Index() { return View(); } }

我们在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方法里

 //给IControllerFactory注册默认的Controller工厂类DefaultControllerFactory //也是Controller创建的入口 services.TryAddSingleton(); //真正创建Controller的工作类DefaultControllerActivator services.TryAddTransient();

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

 internal class DefaultControllerFactory : IControllerFactory { //真正创建Controller的工作者 private readonly IControllerActivator _controllerActivator; private readonly IControllerPropertyActivator[] _propertyActivators; public DefaultControllerFactory( IControllerActivator controllerActivator, IEnumerable propertyActivators) { _controllerActivator = controllerActivator; _propertyActivators = propertyActivators.ToArray(); } ///  /// 创建Controller实例的方法 ///  public object CreateController(ControllerContext context) { //创建Controller实例的具体方法(这是关键方法) var controller = _controllerActivator.Create(context); foreach (var propertyActivator in _propertyActivators) { propertyActivator.Activate(context, controller); } return controller; } ///  /// 释放Controller实例的方法 ///  public void ReleaseController(ControllerContext context, object controller) { _controllerActivator.Release(context, controller); } }

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

 internal class DefaultControllerActivator : IControllerActivator { private readonly ITypeActivatorCache _typeActivatorCache; public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache) { _typeActivatorCache = typeActivatorCache; } ///  /// Controller实例的创建方法 ///  public object Create(ControllerContext controllerContext) { //获取Controller类型信息 var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; //获取ServiceProvider var serviceProvider = controllerContext.HttpContext.RequestServices; //创建controller实例 return _typeActivatorCache.CreateInstance(serviceProvider, controllerTypeInfo.AsType()); } ///  /// 释放Controller实例 ///  public void Release(ControllerContext context, object controller) { //如果controller实现了IDisposable接口,那么Release的时候会自动调用Controller的Dispose方法 //如果我们在Controller中存在需要释放或者关闭的操作,可以再Controller的Dispose方法中统一释放 if (controller is IDisposable disposable) { disposable.Dispose(); } } }

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

 services.TryAddSingleton();

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

 internal class TypeActivatorCache : ITypeActivatorCache { //创建ObjectFactory的委托 private readonly Func _createFactory = (type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes); //Controller类型和对应创建Controller实例的ObjectFactory实例的缓存 private readonly ConcurrentDictionary _typeActivatorCache = new ConcurrentDictionary(); ///  /// 真正创建实例的地方 ///  public TInstance CreateInstance( IServiceProvider serviceProvider, Type implementationType) { //真正创建的操作是createFactory //通过Controller类型在ConcurrentDictionary缓存中获得ObjectFactory //而ObjectFactory实例由ActivatorUtilities.CreateFactory方法创建的 var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory); //返回创建实例 return (TInstance)createFactory(serviceProvider, arguments: null); } }

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

 delegate object ObjectFactory(IServiceProvider serviceProvider, object[] arguments);

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

ActivatorUtilities类的探究

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

 public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) { //查找instanceType的构造函数 //找到构造信息ConstructorInfo //得到给定类型与查找类型instanceType构造函数的映射关系 FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); //构建IServiceProvider类型参数 var provider = Expression.Parameter(typeof(IServiceProvider), "provider"); //构建给定类型参数数组参数 var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); //通过构造信息、构造参数对应关系、容器和给定类型构建表达式树Body var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray); //构建lambda var factoryLamda = Expression.Lambda>( factoryExpressionBody, provider, argumentArray); var result = factoryLamda.Compile(); //返回执行结果 return result.Invoke; }

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

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

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

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

 public static T CreateInstance(IServiceProvider provider, params object[] parameters)

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

 public class OrderController { private readonly IOrderService _orderService; private readonly IPersonService _personService; public OrderController(IOrde