看到标题可能你们会有所疑问Controller和IOC能有啥羁绊,可是我仍是拒绝当一个标题党的。相信有很大一部分人已经知道了这么一个结论,默认状况下ASP.NET Core的Controller并不会托管到IOC容器中,注意关键字我说的是"默认",首先我们不先说为何,若是还有不知道这个结论的同窗们能够本身验证一下,验证方式也很简单,大概能够经过如下几种方式。html
首先,咱们能够尝试在ServiceProvider中获取某个Controller实例,好比git
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var productController = app.ApplicationServices.GetService<ProductController>(); }
这是最直接的方式,能够在IOC容器中获取注册过的类型实例,很显然结果会为null。另外一种方式,也是利用它的另外一个特征,那就是经过构造注入的方式,以下所示咱们在OrderController中注入ProductController,显然这种方式是不合理的,可是为了求证一个结果,咱们这里仅作演示,强烈不建议实际开发中这么写,这是不规范也是不合理的写法github
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中验证一下web
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当中。数组
上面虽然咱们看到了一些现象,能说明Controller默认状况下并不在IOC中托管,可是尚未足够的说服力,接下来咱们就来查看源码,这是最有说服力的。咱们找到Controller工厂注册的地方,在MvcCoreServiceCollectionExtensions扩展类中[点击查看源码👈]的AddMvcCoreServices方法里缓存
//给IControllerFactory注册默认的Controller工厂类DefaultControllerFactory //也是Controller建立的入口 services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>(); //真正建立Controller的工做类DefaultControllerActivator services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
由此咱们能够得出,默认的Controller建立工厂类为DefaultControllerFactory,那么咱们直接找到源码位置[点击查看源码👈],
为了方便阅读,精简一下源码以下所示sass
internal class DefaultControllerFactory : IControllerFactory { //真正建立Controller的工做者 private readonly IControllerActivator _controllerActivator; private readonly IControllerPropertyActivator[] _propertyActivators; public DefaultControllerFactory( IControllerActivator controllerActivator, IEnumerable<IControllerPropertyActivator> propertyActivators) { _controllerActivator = controllerActivator; _propertyActivators = propertyActivators.ToArray(); } /// <summary> /// 建立Controller实例的方法 /// </summary> public object CreateController(ControllerContext context) { //建立Controller实例的具体方法(这是关键方法) var controller = _controllerActivator.Create(context); foreach (var propertyActivator in _propertyActivators) { propertyActivator.Activate(context, controller); } return controller; } /// <summary> /// 释放Controller实例的方法 /// </summary> public void ReleaseController(ControllerContext context, object controller) { _controllerActivator.Release(context, controller); } }
用过上面的源码可知,真正建立Controller的地方在_controllerActivator.Create方法中,经过上面的源码可知为IControllerActivator默认注册的是DefaultControllerActivator类,直接找到源码位置[点击查看源码👈],咱们继续简化一下源码以下所示app
internal class DefaultControllerActivator : IControllerActivator { private readonly ITypeActivatorCache _typeActivatorCache; public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache) { _typeActivatorCache = typeActivatorCache; } /// <summary> /// Controller实例的建立方法 /// </summary> public object Create(ControllerContext controllerContext) { //获取Controller类型信息 var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; //获取ServiceProvider var serviceProvider = controllerContext.HttpContext.RequestServices; //建立controller实例 return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType()); } /// <summary> /// 释放Controller实例 /// </summary> 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<ITypeActivatorCache, TypeActivatorCache>();
有了这个信息,咱们能够直接找到TypeActivatorCache类的源码[点击查看源码👈]代码并很少,大体以下所示ide
internal class TypeActivatorCache : ITypeActivatorCache { //建立ObjectFactory的委托 private readonly Func<Type, ObjectFactory> _createFactory = (type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes); //Controller类型和对应建立Controller实例的ObjectFactory实例的缓存 private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache = new ConcurrentDictionary<Type, ObjectFactory>(); /// <summary> /// 真正建立实例的地方 /// </summary> public TInstance CreateInstance<TInstance>( 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类是建立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<Func<IServiceProvider, object[], object>>( factoryExpressionBody, provider, argumentArray); var result = factoryLamda.Compile(); //返回执行结果 return result.Invoke; }
ActivatorUtilities类的CreateFactory方法代码虽然比较简单,可是它涉及到调用了其余方法,因为嵌套的比较深代码比较多,并且不是本文讲述的重点,咱们就再也不这里细说了,咱们能够大概的描述一下它的工做流程。
public static T CreateInstance<T>(IServiceProvider provider, params object[] parameters)
它能够经过构造注入的方式建立指定类型T的实例,其中构造函数里具体的参数实例是经过在IServiceProvider实例里获取到的,好比咱们咱们有这么一个类
public class OrderController { private readonly IOrderService _orderService; private readonly IPersonService _personService; public OrderController(IOrderService orderService, IPersonService personService) { _orderService = orderService; _personService = personService; } }
其中它所依赖的IOrderService和IPersonService实例是注册到IOC容器中的
IServiceCollection services = new ServiceCollection() .AddScoped<IPersonService, PersonService>() .AddScoped<IOrderService, OrderService>();
而后你想获取到OrderController的实例,可是它只包含一个有参构造函数,可是构造函数的参数都以注册到IOC容器中。当存在这种场景你即可以经过如下方式获得你想要的类型实例,以下所示
IServiceProvider serviceProvider = services.BuildServiceProvider(); OrderController orderController = ActivatorUtilities.CreateInstance<OrderController>(serviceProvider);
即便你的类型OrderController并无注册到IOC容器中,可是它的依赖都在容器中,你也能够经过构造注入的方式获得你想要的实例。总的来讲ActivatorUtilities里的方法仍是比较实用的,有兴趣的同窗能够自行尝试一下,也能够经过查看ActivatorUtilities源码的方式了解它的工做原理。
上面咱们主要是讲解了默认状况下Controller并非托管到IOC容器中的,它只是表现出来的让你觉得它是在IOC容器中,由于它能够经过构造函数注入相关实例,这主要是ActivatorUtilities类的功劳。说了这么多Controller实例到底可不能够注册到IOC容器中,让它成为真正受到IOC容器的托管者。要解决这个,必需要知足两点条件
services.AddMvc().AddControllersAsServices(); //或其余方式,这取决于你构建的Web项目的用途能够是WebApi、Mvc、RazorPage等 //services.AddMvcCore().AddControllersAsServices();
相信你们都看到了,玄机就在AddControllersAsServices方法中,可是它存在于MvcCoreMvcBuilderExtensions类和MvcCoreMvcCoreBuilderExtensions类中,不过问题不大,由于它们的代码是彻底同样的。只是由于你能够经过多种方式构建Web项目好比AddMvc或者AddMvcCore,废话很少说直接上代码[点击查看源码👈]
public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } var feature = new ControllerFeature(); builder.PartManager.PopulateFeature(feature); //第一将Controller实例添加到IOC容器中 foreach (var controller in feature.Controllers.Select(c => c.AsType())) { //注册的声明周期是Transient builder.Services.TryAddTransient(controller, controller); } //第二替换掉本来DefaultControllerActivator的为ServiceBasedControllerActivator builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); return builder; }
第一点没问题那就是将Controller实例添加到IOC容器中,第二点它替换掉了DefaultControllerActivator为为ServiceBasedControllerActivator。经过上面咱们讲述的源码了解到DefaultControllerActivator是默认提供Controller实例的地方是获取Controller实例的核心所在,那么咱们看看ServiceBasedControllerActivator与DefaultControllerActivator到底有何不一样,直接贴出代码[点击查看源码👈]
public class ServiceBasedControllerActivator : IControllerActivator { public object Create(ControllerContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } //获取Controller类型 var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); //经过Controller类型在容器中获取实例 return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); } public virtual void Release(ControllerContext context, object controller) { } }
相信你们对上面的代码一目了然了,和咱们上面描述的同样,将建立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注册为别的生命周期,固然这种方式我的不是很建议。
有时候你们可能会结合Autofac一块儿使用,Autofac确实是一款很是优秀的IOC框架,它它支持属性和构造两种方式注入,关于Autofac托管自带IOC的原理我们在以前的文章浅谈.Net Core DependencyInjection源码探究中曾详细的讲解过,这里我们就不过多的描述了,我们今天要说的是Autofac和Controller的结合。若是你想保持和原有的IOC一致的使用习惯,即只使用构造注入的话,你只须要完成两步便可
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) //只须要在这里设置ServiceProviderFactory为AutofacServiceProviderFactory便可 .UseServiceProviderFactory(new AutofacServiceProviderFactory());
services.AddMvc().AddControllersAsServices();
只须要经过上面简单得两步,既能够将Controller托管到Autofac容器中。可是,咱们说过了Autofac还支持属性注入,可是默认的方式只支持构造注入的方式,那么怎么让Controller支持属性注入呢?咱们还得从最根本的出发,那就是解决Controller实例存和取的问题
public void ConfigureContainer(ContainerBuilder builder) { var controllerBaseType = typeof(ControllerBase); //扫描Controller类 builder.RegisterAssemblyTypes(typeof(Program).Assembly) .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) //属性注入 .PropertiesAutowired(); }
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
仅须要在默认的状态下完成这两步,既能够解决Controller托管到Autofac中并支持属性注入的问题,这也是最合理的方式。固然若是你使用AddControllersAsServices但是能够实现相同的效果了,只不过是不必将容器重复的放入容器中了。
本文咱们讲述了关于ASP.NET Core Controller与IOC结合的问题,我以为这是有必要让每一个人都有所了解的知识点,由于在平常的Web开发中Controller太经常使用了,知道这个问题可能会让你们在开发中少走一点弯路,接下来咱们来总结一下本文大体讲解的内容
本次讲解到这里就差很少了,但愿原本就知道的同窗们能加深一点了解,不知道的同窗可以给大家提供一点帮助,可以在平常开发中少走一点弯路。新的一年开始了,本篇文章是我2021年的第一篇文章,新的一年感谢你们的支持。