日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core Controller与IOC的羁绊

發(fā)布時間:2023/12/4 asp.net 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core Controller与IOC的羁绊 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

????看到標(biāo)題可能大家會有所疑問Controller和IOC能有啥羈絆,但是我還是拒絕當(dāng)一個標(biāo)題黨的。相信有很大一部分人已經(jīng)知道了這么一個結(jié)論,默認(rèn)情況下ASP.NET Core的Controller并不會托管到IOC容器中,注意關(guān)鍵字我說的是"默認(rèn)",首先咱們不先說為什么,如果還有不知道這個結(jié)論的同學(xué)們可以自己驗證一下,驗證方式也很簡單,大概可以通過以下幾種方式。

驗證Controller不在IOC中

首先,我們可以嘗試在ServiceProvider中獲取某個Controller實例,比如

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

這是最直接的方式,可以在IOC容器中獲取注冊過的類型實例,很顯然結(jié)果會為null。另一種方式,也是利用它的另一個特征,那就是通過構(gòu)造注入的方式,如下所示我們在OrderController中注入ProductController,顯然這種方式是不合理的,但是為了求證一個結(jié)果,我們這里僅做演示,強烈不建議實際開發(fā)中這么寫,這是不規(guī)范也是不合理的寫法

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

結(jié)果顯然是會報一個錯InvalidOperationException: Unable to resolve service for type 'ProductController' while attempting to activate 'OrderController'。原因就是因為ProductController并不在IOC容器中,所以通過注入的方式會報錯。還有一種方式,可能不太常用,這個是利用注入的一個特征,可能有些同學(xué)已經(jīng)了解過了,那就是通過自帶的DI,即使一個類中包含多個構(gòu)造函數(shù),它也會選擇最優(yōu)的一個,也就是說自帶的DI允許類包含多個構(gòu)造函數(shù)。利用這個特征,我們可以在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中編寫了兩個構(gòu)造函數(shù),理論上來說這是符合DI特征的,運行起來測試一下,依然會報錯InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'OrderController'. There should only be one applicable constructor。以上種種都是為了證實一個結(jié)論,默認(rèn)情況下Controller并不會托管到IOC當(dāng)中。

DefaultControllerFactory源碼探究

????上面雖然我們看到了一些現(xiàn)象,能說明Controller默認(rèn)情況下并不在IOC中托管,但是還沒有足夠的說服力,接下來我們就來查看源碼,這是最有說服力的。我們找到Controller工廠注冊的地方,在MvcCoreServiceCollectionExtensions擴展類中[點擊查看源碼????]的AddMvcCoreServices方法里

//給IControllerFactory注冊默認(rèn)的Controller工廠類DefaultControllerFactory //也是Controller創(chuàng)建的入口 services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>(); //真正創(chuàng)建Controller的工作類DefaultControllerActivator services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();

由此我們可以得出,默認(rèn)的Controller創(chuàng)建工廠類為DefaultControllerFactory,那么我們直接找到源碼位置[點擊查看源碼????],
為了方便閱讀,精簡一下源碼如下所示

internal class DefaultControllerFactory : IControllerFactory {//真正創(chuàng)建Controller的工作者private readonly IControllerActivator _controllerActivator;private readonly IControllerPropertyActivator[] _propertyActivators;public DefaultControllerFactory(IControllerActivator controllerActivator,IEnumerable<IControllerPropertyActivator> propertyActivators){_controllerActivator = controllerActivator;_propertyActivators = propertyActivators.ToArray();}/// <summary>/// 創(chuàng)建Controller實例的方法/// </summary>public object CreateController(ControllerContext context){//創(chuàng)建Controller實例的具體方法(這是關(guān)鍵方法)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);} }

用過上面的源碼可知,真正創(chuàng)建Controller的地方在_controllerActivator.Create方法中,通過上面的源碼可知為IControllerActivator默認(rèn)注冊的是DefaultControllerActivator類,直接找到源碼位置[點擊查看源碼????],我們繼續(xù)簡化一下源碼如下所示

internal class DefaultControllerActivator : IControllerActivator {private readonly ITypeActivatorCache _typeActivatorCache;public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache){_typeActivatorCache = typeActivatorCache;}/// <summary>/// Controller實例的創(chuàng)建方法/// </summary>public object Create(ControllerContext controllerContext){//獲取Controller類型信息var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo;//獲取ServiceProvidervar serviceProvider = controllerContext.HttpContext.RequestServices;//創(chuàng)建controller實例return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());}/// <summary>/// 釋放Controller實例/// </summary>public void Release(ControllerContext context, object controller){//如果controller實現(xiàn)了IDisposable接口,那么Release的時候會自動調(diào)用Controller的Dispose方法//如果我們在Controller中存在需要釋放或者關(guān)閉的操作,可以再Controller的Dispose方法中統(tǒng)一釋放if (controller is IDisposable disposable){disposable.Dispose();}} }

通過上面的代碼我們依然要繼續(xù)深入到ITypeActivatorCache實現(xiàn)中去尋找答案,通過查看MvcCoreServiceCollectionExtensions類的AddMvcCoreServices方法源碼我們可以找到如下信息

services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();

有了這個信息,我們可以直接找到TypeActivatorCache類的源碼[點擊查看源碼????]代碼并不多,大致如下所示

internal class TypeActivatorCache : ITypeActivatorCache {//創(chuàng)建ObjectFactory的委托private readonly Func<Type, ObjectFactory> _createFactory =(type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes);//Controller類型和對應(yīng)創(chuàng)建Controller實例的ObjectFactory實例的緩存private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache =new ConcurrentDictionary<Type, ObjectFactory>();/// <summary>/// 真正創(chuàng)建實例的地方/// </summary>public TInstance CreateInstance<TInstance>(IServiceProvider serviceProvider,Type implementationType){//真正創(chuàng)建的操作是createFactory//通過Controller類型在ConcurrentDictionary緩存中獲得ObjectFactory//而ObjectFactory實例由ActivatorUtilities.CreateFactory方法創(chuàng)建的var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory);//返回創(chuàng)建實例return (TInstance)createFactory(serviceProvider, arguments: null);} }

通過上面類的代碼我們可以清晰的得出一個結(jié)論,默認(rèn)情況下Controller實例是由ObjectFactory創(chuàng)建出來的,而ObjectFactory實例是由ActivatorUtilities的CreateFactory創(chuàng)建出來,所以Controller實例每次都是由ObjectFactory創(chuàng)建而來,并非注冊到IOC容器中。并且我們還可以得到一個結(jié)論ObjectFactory應(yīng)該是一個委托,我們找到ObjectFactory定義的地方[點擊查看源碼????]

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

這個確實如我們猜想的那般,這個委托會通過IServiceProvider實例去構(gòu)建類型的實例,通過上述源碼相關(guān)的描述我們會產(chǎn)生一個疑問,既然Controller實例并非由IOC容器托管,它由ObjectFactory創(chuàng)建而來,但是ObjectFactory實例又是由ActivatorUtilities構(gòu)建的,那么生產(chǎn)對象的核心也就在ActivatorUtilities類中,接下來我們就來探究一下ActivatorUtilities的神秘面紗。

ActivatorUtilities類的探究

????書接上面,我們知道了ActivatorUtilities類是創(chuàng)建Controller實例最底層的地方,那么ActivatorUtilities到底和容器是啥關(guān)系,因為我們看到了ActivatorUtilities創(chuàng)建實例需要依賴ServiceProvider,一切都要從找到ActivatorUtilities類的源碼開始。我們最初接觸這個類的地方在于它通過CreateFactory方法創(chuàng)建了ObjectFactory實例,那么我們就從這個地方開始,找到源碼位置[點擊查看源碼????]實現(xiàn)如下

public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) {//查找instanceType的構(gòu)造函數(shù)//找到構(gòu)造信息ConstructorInfo//得到給定類型與查找類型instanceType構(gòu)造函數(shù)的映射關(guān)系FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap);//構(gòu)建IServiceProvider類型參數(shù)var provider = Expression.Parameter(typeof(IServiceProvider), "provider");//構(gòu)建給定類型參數(shù)數(shù)組參數(shù)var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray");//通過構(gòu)造信息、構(gòu)造參數(shù)對應(yīng)關(guān)系、容器和給定類型構(gòu)建表達(dá)式樹Bodyvar factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray);//構(gòu)建lambdavar factoryLamda = Expression.Lambda<Func<IServiceProvider, object[], object>>(factoryExpressionBody, provider, argumentArray);var result = factoryLamda.Compile();//返回執(zhí)行結(jié)果return result.Invoke; }

ActivatorUtilities類的CreateFactory方法代碼雖然比較簡單,但是它涉及到調(diào)用了其他方法,由于嵌套的比較深代碼比較多,而且不是本文講述的重點,我們就不再這里細(xì)說了,我們可以大概的描述一下它的工作流程。

  • 首先在給定的類型里查找到合適的構(gòu)造函數(shù),這里我們可以理解為查找Controller的構(gòu)造函數(shù)。

  • 然后得到構(gòu)造信息,并得到構(gòu)造函數(shù)的參數(shù)與給定類型參數(shù)的對應(yīng)關(guān)系

  • 通過構(gòu)造信息和構(gòu)造參數(shù)的對應(yīng)關(guān)系,在IServiceProvider得到對應(yīng)類型的實例為構(gòu)造函數(shù)賦值

  • 最后經(jīng)過上面的操作通過初始化指定的構(gòu)造函數(shù)來創(chuàng)建給定Controller類型的實例
    綜上述的相關(guān)步驟,我們可以得到一個結(jié)論,Controller實例的初始化是通過遍歷Controller類型構(gòu)造函數(shù)里的參數(shù),然后根據(jù)構(gòu)造函數(shù)每個參數(shù)的類型在IServiceProvider查找已經(jīng)注冊到容器中相關(guān)的類型實例,最終初始化得到的Controller實例。這就是在IServiceProvider得到需要的依賴關(guān)系,然后創(chuàng)建自己的實例,它內(nèi)部是使用的表達(dá)式樹來完成的這一切,可以理解為更高效的反射方式。
    關(guān)于ActivatorUtilities類還包含了其他比較實用的方法,比如CreateInstance方法

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

它可以通過構(gòu)造注入的方式創(chuàng)建指定類型T的實例,其中構(gòu)造函數(shù)里具體的參數(shù)實例是通過在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的實例,但是它只包含一個有參構(gòu)造函數(shù),但是構(gòu)造函數(shù)的參數(shù)都以注冊到IOC容器中。當(dāng)存在這種場景你便可以通過以下方式得到你想要的類型實例,如下所示

IServiceProvider serviceProvider = services.BuildServiceProvider(); OrderController?orderController?=?ActivatorUtilities.CreateInstance<OrderController>(serviceProvider);

即使你的類型OrderController并沒有注冊到IOC容器中,但是它的依賴都在容器中,你也可以通過構(gòu)造注入的方式得到你想要的實例。總的來說ActivatorUtilities里的方法還是比較實用的,有興趣的同學(xué)可以自行嘗試一下,也可以通過查看ActivatorUtilities源碼的方式了解它的工作原理。

AddControllersAsServices方法

????上面我們主要是講解了默認(rèn)情況下Controller并不是托管到IOC容器中的,它只是表現(xiàn)出來的讓你以為它是在IOC容器中,因為它可以通過構(gòu)造函數(shù)注入相關(guān)實例,這主要是ActivatorUtilities類的功勞。說了這么多Controller實例到底可不可以注冊到IOC容器中,讓它成為真正受到IOC容器的托管者。要解決這個,必須要滿足兩點條件

  • 首先,需要將Controller注冊到IOC容器中,但是僅僅這樣還不夠,因為Controller是由ControllerFactory創(chuàng)建而來

  • 其次,我們要改造ControllerFactory類中創(chuàng)建Controller實例的地方讓它從容器中獲取Controller實例,這樣就解決了所有的問題
    如果我們自己去實現(xiàn)將Controller托管到IOC容器中,就需要滿足以上兩個操作一個是要將Controller放入容器,然后讓創(chuàng)建Controller的地方從IOC容器中直接獲取Controller實例。慶幸的是,微軟幫我們封裝了一個相關(guān)的方法,它可以幫我們解決將Controller托管到IOC容器的問題,它的使用方法如下所示

services.AddMvc().AddControllersAsServices(); //或其他方式,這取決于你構(gòu)建的Web項目的用途可以是WebApi、Mvc、RazorPage等 //services.AddMvcCore().AddControllersAsServices();

相信大家都看到了,玄機就在AddControllersAsServices方法中,但是它存在于MvcCoreMvcBuilderExtensions類和MvcCoreMvcCoreBuilderExtensions類中,不過問題不大,因為它們的代碼是完全一樣的。只是因為你可以通過多種方式構(gòu)建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())){//注冊的聲明周期是Transientbuilder.Services.TryAddTransient(controller, controller);}//第二替換掉原本DefaultControllerActivator的為ServiceBasedControllerActivatorbuilder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());return builder; }

第一點沒問題那就是將Controller實例添加到IOC容器中,第二點它替換掉了DefaultControllerActivator為為ServiceBasedControllerActivator。通過上面我們講述的源碼了解到DefaultControllerActivator是默認(rèn)提供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){} }

相信大家對上面的代碼一目了然了,和我們上面描述的一樣,將創(chuàng)建Controller實例的地方改造了在容器中獲取的方式。不知道大家有沒有注意到ServiceBasedControllerActivator的Release的方法居然沒有實現(xiàn),這并不是我沒有粘貼出來,確實是沒有代碼,之前我們看到的DefaultControllerActivator可是有調(diào)用Controller的Disposed的方法,這里卻啥也沒有。相信聰明的你已經(jīng)想到了,因為Controller已經(jīng)托管到了IOC容器中,所以他的生命及其相關(guān)釋放都是由IOC容器完成的,所以這里不需要任何操作。
????我們上面還看到了注冊Controller實例的時候使用的是TryAddTransient方法,也就是說每次都會創(chuàng)建Controller實例,至于為什么,我想大概是因為每次請求都其實只會需要一個Controller實例,況且EFCore的注冊方式官方建議也是Scope的,而這里的Scope正是對應(yīng)的一次Controller請求。在加上自帶的IOC會提升依賴類型的聲明周期,如果將Controller注冊為單例的話如果使用了EFCore那么它也會被提升為單例,這樣會存在很大的問題。也許正是基于這個原因默認(rèn)才將Controller注冊為Transient類型的,當(dāng)然這并不代表只能注冊為Transient類型的,如果你不使用類似EFCore這種需要作用域為Scope的服務(wù)的時候,而且保證使用的主鍵都可以使用單例的話,完全可以將Controller注冊為別的生命周期,當(dāng)然這種方式個人不是很建議。

Controller結(jié)合Autofac

????有時候大家可能會結(jié)合Autofac一起使用,Autofac確實是一款非常優(yōu)秀的IOC框架,它它支持屬性和構(gòu)造兩種方式注入,關(guān)于Autofac托管自帶IOC的原理咱們在之前的文章淺談.Net Core DependencyInjection源碼探究中曾詳細(xì)的講解過,這里咱們就不過多的描述了,咱們今天要說的是Autofac和Controller的結(jié)合。如果你想保持和原有的IOC一致的使用習(xí)慣,即只使用構(gòu)造注入的話,你只需要完成兩步即可

  • 首先將默認(rèn)的IOC容器替換為Autofac,具體操作也非常簡單,如下所示

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();})//只需要在這里設(shè)置ServiceProviderFactory為AutofacServiceProviderFactory即可.UseServiceProviderFactory(new AutofacServiceProviderFactory());
  • 然后就是咱們之前說的,要將Controller放入容器中,然后修改生產(chǎn)Controller實例的ControllerFactory的操作為在容器中獲取,當(dāng)然這一步微軟已經(jīng)為我們封裝了便捷的方法

services.AddMvc().AddControllersAsServices();

只需要通過上面簡單得兩步,既可以將Controller托管到Autofac容器中。但是,我們說過了Autofac還支持屬性注入,但是默認(rèn)的方式只支持構(gòu)造注入的方式,那么怎么讓Controller支持屬性注入呢?我們還得從最根本的出發(fā),那就是解決Controller實例存和取的問題

  • 首先為了讓Controller托管到Autofac中并且支持屬性注入,那么就只能使用Autofac的方式去注冊Controller實例,具體操作是在Startup類中添加ConfigureContainer方法,然后注冊Controller并聲明支持屬性注入

public void ConfigureContainer(ContainerBuilder builder) {var controllerBaseType = typeof(ControllerBase);//掃描Controller類builder.RegisterAssemblyTypes(typeof(Program).Assembly).Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)//屬性注入.PropertiesAutowired(); }
  • 其次是解決取的問題,這里我們就不需要AddControllersAsServices方法了,因為AddControllersAsServices解決了Controller實例在IOC中存和取的問題,但是這里我們只需要解決Controller取得問題說只需要使用ServiceBasedControllerActivator即可,具體操作是

services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

僅需要在默認(rèn)的狀態(tài)下完成這兩步,既可以解決Controller托管到Autofac中并支持屬性注入的問題,這也是最合理的方式。當(dāng)然如果你使用AddControllersAsServices可是可以實現(xiàn)相同的效果了,只不過是沒必要將容器重復(fù)的放入容器中了。

總結(jié)

????本文我們講述了關(guān)于ASP.NET Core Controller與IOC結(jié)合的問題,我覺得這是有必要讓每個人都有所了解的知識點,因為在日常的Web開發(fā)中Controller太常用了,知道這個問題可能會讓大家在開發(fā)中少走一點彎路,接下來我們來總結(jié)一下本文大致講解的內(nèi)容

  • 首先說明了一個現(xiàn)象,那就是默認(rèn)情況下Controller并不在IOC容器中,我們也通過幾個示例驗證了一下。

  • 其次講解了默認(rèn)情況下創(chuàng)造Controller實例真正的類ActivatorUtilities,并大致講解了ActivatorUtilities的用途。

  • 然后我們找到了將Controller托管到IOC容器中的辦法AddControllersAsServices,并探究了它的源碼,了解了它的工作方式。

  • 最后我們又演示了如何使用最合理的方式將Controller結(jié)合Autofac一起使用,并且支持屬性注入。

本次講解到這里就差不多了,希望本來就知道的同學(xué)們能加深一點了解,不知道的同學(xué)能夠給你們提供一點幫助,能夠在日常開發(fā)中少走一點彎路。新的一年開始了,本篇文章是我2021年的第一篇文章,新的一年感謝大家的支持。

????歡迎掃碼關(guān)注我的公眾號????

總結(jié)

以上是生活随笔為你收集整理的ASP.NET Core Controller与IOC的羁绊的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。