看到標題可能你們會有所疑問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年的第一篇文章,新的一年感謝你們的支持。