乱码乱a∨中文字幕,在线免费激情视频,亚洲欧美久久夜夜潮,国产在线网址

  1. <sub id="hjl7n"></sub>

    1. <sub id="hjl7n"></sub>

      <legend id="hjl7n"></legend>

      當(dāng)前位置:首頁 >  站長 >  編程技術(shù) >  正文

      深入探究ASP.NET Core Startup初始化問題

       2020-12-11 16:11  來源: 腳本之家   我來投稿 撤稿糾錯(cuò)

        阿里云優(yōu)惠券 先領(lǐng)券再下單

      這篇文章主要介紹了深入探究ASP.NET Core Startup初始化問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

      前言

      Startup類相信大家都比較熟悉,在我們使用ASP.NET Core開發(fā)過程中經(jīng)常用到的類,我們通常使用它進(jìn)行IOC服務(wù)注冊,配置中間件信息等。雖然它不是必須的,但是將這些操作統(tǒng)一在Startup中做處理,會在實(shí)際開發(fā)中帶來許多方便。當(dāng)我們談起Startup類的時(shí)候你有沒有好奇過以下幾點(diǎn)

      為何我們自定義的Startup可以正常工作。

      我們定義的Startup類中ConfigureServices和Configure只能叫這個(gè)名字才能被調(diào)用到嗎?

      在使用泛型主機(jī)(IHostBuilder)時(shí)Startup的構(gòu)造函數(shù),為何只支持注入IWebHostEnvironment、IHostEnvironment、IConfiguration。

      ConfigureServices方法為何只能傳遞IServiceCollection實(shí)例。

      Configure方法的參數(shù)為何可以是所有在IServiceCollection注冊服務(wù)實(shí)例。

      在ASP.NET Core結(jié)合Autofac使用的時(shí)候?yàn)楹挝覀兲砑拥腃onfigureContainer方法會被調(diào)用。

      帶著以上幾點(diǎn)疑問,我們將在本篇文章中探索Startup的源碼,來了解Startup初始化過程到底為我們做了些什么。

      Startup的另類指定方式

      在日常編碼過程中,我們通常使用UseStartup的方式來引入Startup類。但是這并不是唯一的方式,還有一種方式是在配置節(jié)點(diǎn)中指定Startup所在的程序集來自動(dòng)查找Startup類,這個(gè)我們可以在GenericWebHostBuilder的構(gòu)造函數(shù)源碼中的找到相關(guān)代碼[點(diǎn)擊查看源碼]相信熟悉ASP.Net Core啟動(dòng)流程的同學(xué)對GenericWebHostBuilder這個(gè)類都比較了解。ConfigureWebHostDefaults方法中其實(shí)調(diào)用了ConfigureWebHost方法,ConfigureWebHost方法中實(shí)例化了GenericWebHostBuilder對象,啟動(dòng)流程不是咱們的重點(diǎn),所以這里只是簡單描述一下。直接找到我們需要的代碼如下所示

      //判斷是否配置了StartupAssembly參數(shù)
      if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
      {
      try
      {
      //根據(jù)你配置的程序集去查找Startup
      var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
      UseStartup(startupType, context, services);
      }
      catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
      {
      //此處省略代碼省略
      }
      }

      這里我們可以看出來,我們需要配置StartupAssembly對應(yīng)的程序集,它可以通過StartupLoader的FindStartupType方法加載程序集中對應(yīng)的類。我們還可以看到它還傳遞了EnvironmentName環(huán)境變量,至于它起到了什么作用,我們繼續(xù)往下看。

      首先我們需要找到webHostOptions.StartupAssembly是如何被初始化的,在WebHostOptions的構(gòu)造函數(shù)中我們找到了StartupAssembly初始化的地方[點(diǎn)擊查看源碼]

      StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey];

      從這里也可以看出來它的值來于配置,它的key來自WebHostDefaults.StartupAssemblyKey這個(gè)常量值,最后我們找到了的值為

      public static readonly string StartupAssemblyKey = "startupAssembly";

      也就是說只要我們給startupAssembly配置Startup所在的程序集名稱,它就可以在程序集中查找Startup類進(jìn)行初始化,如下所示

      public static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
      .ConfigureHostConfiguration(config=> {
      List<KeyValuePair<string, string>> keyValuePairs = new List<KeyValuePair<string, string>>();
      //配置Startup所在的程序集名稱
      keyValuePairs.Add(new KeyValuePair<string, string>("startupAssembly", "Startup所在的程序集名稱"));
      config.AddInMemoryCollection(keyValuePairs);
      })
      .ConfigureWebHostDefaults(webBuilder =>
      {
      //這樣的話這里就可以省略了
      //webBuilder.UseStartup<Startup>();
      });

      回到上面的思路,我們在StartupLoader類中查看FindStartupType方法,來看下它是通過什么規(guī)則來查找Startup的[點(diǎn)擊查看源碼]精簡之后的代碼大致如下

      public static Type FindStartupType(string startupAssemblyName, string environmentName)
      {
      var assembly = Assembly.Load(new AssemblyName(startupAssemblyName));
      //名稱Startup+環(huán)境變量的類比如(StartupDevelopment)
      var startupNameWithEnv = "Startup" + environmentName;
      //名稱為Startup的類
      var startupNameWithoutEnv = "Startup";

      // 先查找包含名稱Startup+環(huán)境變量的相關(guān)類,如果找不到則查找名稱為Startup的類
      var type =
      assembly.GetType(startupNameWithEnv) ??
      assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ??
      assembly.GetType(startupNameWithoutEnv) ??
      assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv);

      if (type == null)
      {
      // 如果上述規(guī)則找不到,則在程序集定義的所有類中繼續(xù)查找
      var definedTypes = assembly.DefinedTypes.ToList();

      var startupType1 = definedTypes.Where(info => info.Name.Equals(startupNameWithEnv, StringComparison.OrdinalIgnoreCase));
      var startupType2 = definedTypes.Where(info => info.Name.Equals(startupNameWithoutEnv, StringComparison.OrdinalIgnoreCase));

      var typeInfo = startupType1.Concat(startupType2).FirstOrDefault();
      if (typeInfo != null)
      {
      type = typeInfo.AsType();
      }
      }
      //最終返回Startup類型
      return type;
      }

      通過上述代碼我們可以看到在通過配置指定程序集時(shí)是如何查找指定規(guī)則的Startup類的,基本上可以理解為先去查找名稱為Startup+環(huán)境變量的類,如果找不到則繼續(xù)查找名稱為Startup的類,最終會返回Startup的類型傳遞給UseStartup方法。其實(shí)我們最常使用的UseStartup()方法最終也是轉(zhuǎn)換成UseStartup(typeof(T))的方式,所以最終這兩種方式走到了相同的地方,接下來我們步入正題,來一起探究一下Starup究竟是如何被初始化的。

      Startup的構(gòu)造函數(shù)

      相信對Startup有所了解的同學(xué)們都比較清楚,在使用泛型主機(jī)(IHostBuilder)時(shí)Startup的構(gòu)造函數(shù)只支持注入IWebHostEnvironment、IHostEnvironment、IConfiguration,這個(gè)在微軟官方文檔中https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1也有介紹,如果還有不熟悉這個(gè)操作的請先反思一下自己,然后在查閱微軟官方文檔。接下來我們就從源碼著手,來探究一下它到底是如何做到的。沿著上述的操作,繼續(xù)查看UseStartup里的代碼找到了如下的實(shí)現(xiàn)[點(diǎn)擊查看源碼]

      //創(chuàng)建Startup實(shí)例
      object instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);

      這里的startupType就是我們傳遞的Startup類型,關(guān)于ActivatorUtilities這個(gè)類還是比較實(shí)用的,它為我們提供了許多幫助我們實(shí)例化對象的方法,在日常編程中如果有需要可以使用這個(gè)類。上面的ActivatorUtilities的CreateInstance方法的功能就是根據(jù)傳遞IServiceProvider類型的對象去實(shí)例化指定的類型對象,我們這里的類型就是startupType。它的使用場景就是,如果某個(gè)類型需要用過有參構(gòu)造函數(shù)去實(shí)例化,而構(gòu)造函數(shù)的參數(shù)可以來自于IServiceProvider的實(shí)例,那么使用這個(gè)方法就在合適不過了。上面的代碼傳遞的IServiceProvider的實(shí)例是HostServiceProvider對象,接下來我們找到它的實(shí)現(xiàn)源碼[點(diǎn)擊查看源碼]代碼并不多我們就全部粘貼出來

      private class HostServiceProvider : IServiceProvider
      {
      private readonly WebHostBuilderContext _context;
      public HostServiceProvider(WebHostBuilderContext context)
      {
      _context = context;
      }

      public object GetService(Type serviceType)
      {
      // 通過這里我們就比較清晰的看出,只有滿足這幾種情況下才能返回具體的實(shí)例,其他的都會返回null
      #pragma warning disable CS0618 // Type or member is obsolete
      if (serviceType == typeof(Microsoft.Extensions.Hosting.IHostingEnvironment)
      || serviceType == typeof(Microsoft.AspNetCore.Hosting.IHostingEnvironment)
      #pragma warning restore CS0618 // Type or member is obsolete
      || serviceType == typeof(IWebHostEnvironment)
      || serviceType == typeof(IHostEnvironment)
      )
      {
      return _context.HostingEnvironment;
      }
      if (serviceType == typeof(IConfiguration))
      {
      return _context.Configuration;
      }
      //不滿足這幾種情況的類型都返回null
      return null;
      }
      }

      通過這個(gè)內(nèi)部私有類我們就能清晰的看到為何Starup的構(gòu)造函數(shù)只能注入IWebHostEnvironment、IHostEnvironment、IConfiguration相關(guān)實(shí)例了,HostServiceProvider類實(shí)現(xiàn)了IServiceProvider的GetService方法并做了判斷,只有滿足這幾種類型才能返回具體的實(shí)例注入,其它不滿足條件的類型都會返回null。因此在初始化Starup實(shí)例的時(shí)候,通過構(gòu)造函數(shù)注入的類型也就只能是這幾種了。最終通過這個(gè)構(gòu)造函數(shù)初始化了Startup類的實(shí)例。

      ConfigureServices的裝載

      接下來我們就來在UseStartup方法里繼續(xù)查看是如何查找并執(zhí)行ConfigureServices方法的,繼續(xù)查看找到如下實(shí)現(xiàn)[點(diǎn)擊查看源碼]

      //傳遞startupType和環(huán)境變量參數(shù)查找返回ConfigureServicesBuilder
      var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName);
      //調(diào)用Build方法返回ConfigureServices委托
      var configureServices = configureServicesBuilder.Build(instance);
      //傳遞services對象即IServiceCollection對象調(diào)用ConfigureServices方法
      configureServices(services);

      從上述代碼中我們可以了解到查找并執(zhí)行ConfigureServices方法的具體步驟可分為三步,首先在startupType類型中根據(jù)環(huán)境變量名稱查找具體方法返回ConfigureServicesBuilder實(shí)例,然后構(gòu)建ConfigureServicesBuilder實(shí)例返回ConfigureServices方法的委托,最后傳遞IServiceCollection對象執(zhí)行委托方法。接下來我們就來查看具體實(shí)現(xiàn)源碼。

      我們在StartupLoader類中找到了FindConfigureServicesDelegate方法的相關(guān)實(shí)現(xiàn)[點(diǎn)擊查看源碼]

      internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
      {
      //根據(jù)startupType和根據(jù)environmentName構(gòu)建的Configure{0}Services字符串先去查找返回類型為IServiceProvider的方法
      //找不到在查找返回值為void類型的方法
      var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
      ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
      //根據(jù)查找的到的MethodInfo去構(gòu)建ConfigureServicesBuilder實(shí)例
      return new ConfigureServicesBuilder(servicesMethod);
      }

      通過這里的源碼我們可以看到在startupType類型里去查找名字為environmentName構(gòu)建的Configure{0}Services的方法信息,然后根據(jù)查找的方法信息即MethodInfo對象去構(gòu)建ConfigureServicesBuilder實(shí)例。接下里我們就來查詢FindMethod方法的實(shí)現(xiàn)

      private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
      {
      //包含環(huán)境變量的ConfigureServices方法名稱比如(ConfigureDevelopmentServices)
      var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);
      //名為ConfigureServices的方法
      var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");
      //方法是共有的靜態(tài)的或非靜態(tài)的方法
      var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
      //查找包含環(huán)境變量的ConfigureServices方法名稱
      var selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase)).ToList();
      if (selectedMethods.Count > 1)
      {
      //找打多個(gè)滿足規(guī)則的方法直接拋出異常
      throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithEnv));

      }
      //如果不存在包含環(huán)境變量的ConfigureServices的方法比如(ConfigureDevelopmentServices),則直接查找方法名為ConfigureServices的方法
      if (selectedMethods.Count == 0)
      {
      selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase)).ToList();
      //如果存在多個(gè)則同樣拋出異常
      if (selectedMethods.Count > 1)
      {
      throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithNoEnv));
      }
      }

      var methodInfo = selectedMethods.FirstOrDefault();
      //如果沒找到滿足規(guī)則的方法,并且滿足required參數(shù),則拋出未找到方法的異常
      if (methodInfo == null)
      {
      if (required)
      {
      throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.",
      methodNameWithEnv,
      methodNameWithNoEnv,
      startupType.FullName));

      }
      return null;
      }
      //如果找到了名稱一致的方法,但是返回類型和預(yù)期的不一致,也拋出異常
      if (returnType != null && methodInfo.ReturnType != returnType)
      {
      if (required)
      {
      throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.",
      methodInfo.Name,
      startupType.FullName,
      returnType.Name));
      }
      return null;
      }
      return methodInfo;
      }

      通過FindMethod方法我們可以得到幾個(gè)結(jié)論,首先ConfigureServices方法的名稱可以是包含環(huán)境變量的名稱比如(ConfigureDevelopmentServices),其次方法可以為共有的靜態(tài)或非靜態(tài)方法。FindMethod方法是真正執(zhí)行查找的邏輯所在,如果找到相關(guān)方法則返回MethodInfo。FindMethod查找的方法名稱是通過methodName參數(shù)傳遞進(jìn)來的,我們標(biāo)注的注釋代碼都是直接寫死了ConfigureServices方法,只是為了便于說明理解,但其實(shí)FindMethod是通用方法,接下來我們要講解的內(nèi)容還會涉及到這個(gè)方法,到時(shí)候關(guān)于這個(gè)代碼的邏輯我們就不會在進(jìn)行說明了,因?yàn)槭峭粋€(gè)方法,希望大家能注意到這一點(diǎn)。

      通過上面的相關(guān)方法,我們了解到了是通過什么樣的規(guī)則去查找到ConfigureServices的方法信息的,我們也看到了ConfigureServicesBuilder正是通過查找到的MethodInfo去構(gòu)造實(shí)例的,接下來我們就來查看下ConfigureServicesBuilder的實(shí)現(xiàn)源碼[點(diǎn)擊查看源碼]

      internal class ConfigureServicesBuilder
      {
      //構(gòu)造函數(shù)傳遞的configureServices的MethodInfo
      public ConfigureServicesBuilder(MethodInfo configureServices)
      {
      MethodInfo = configureServices;
      }

      public MethodInfo MethodInfo { get; }
      public Func<Func<IServiceCollection, IServiceProvider>, Func<IServiceCollection, IServiceProvider>> StartupServiceFilters { get; set; } = f => f;
      //Build委托
      public Func<IServiceCollection, IServiceProvider> Build(object instance) => services => Invoke(instance, services);
      private IServiceProvider Invoke(object instance, IServiceCollection services)
      {
      //執(zhí)行StartupServiceFilters委托參數(shù)為Func<IServiceCollection, IServiceProvider>類型的委托方法即Startup
      //返回了Func<IServiceCollection, IServiceProvider>委托,執(zhí)行這個(gè)委托需傳遞services即IServiceCollections實(shí)例返回IServiceProvider類型
      return StartupServiceFilters(Startup)(services);
      IServiceProvider Startup(IServiceCollection serviceCollection) => InvokeCore(instance, serviceCollection);
      }

      private IServiceProvider InvokeCore(object instance, IServiceCollection services)
      {
      if (MethodInfo == null)
      {
      return null;
      }
      // 如果ConfigureServices方法包含多個(gè)參數(shù)或方法參數(shù)類型不是IServiceCollection類型則直接拋出異常
      // 也就是說ConfigureServices只能包含一個(gè)參數(shù)且類型為IServiceCollection
      var parameters = MethodInfo.GetParameters();
      if (parameters.Length > 1 ||
      parameters.Any(p => p.ParameterType != typeof(IServiceCollection)))
      {
      throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection.");
      }
      //找到ConfigureServices方法的參數(shù),并將services即IServiceCollection的實(shí)例傳遞給這個(gè)參數(shù)
      var arguments = new object[MethodInfo.GetParameters().Length];
      if (parameters.Length > 0)
      {
      arguments[0] = services;
      }
      // 執(zhí)行返回IServiceProvider實(shí)例
      return MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments) as IServiceProvider;
      }
      }

      看完ConfigureServicesBuilder類的實(shí)現(xiàn)邏輯,關(guān)于通過什么樣的邏輯查找并執(zhí)行ConfigureServices方法的邏輯就非常清晰了。首先是查找ConfigureServices方法,即包含環(huán)境變量的ConfigureServices方法名稱比如(ConfigureDevelopmentServices)或名為ConfigureServices的方法,返回的是ConfigureServicesBuilder對象。然后執(zhí)行ConfigureServicesBuilder的Build方法,這個(gè)方法里包含了執(zhí)行ConfigureServices的規(guī)則,即ConfigureServices只能包含一個(gè)參數(shù)且類型為IServiceCollection,然后將當(dāng)前程序中存在的IServiceCollection實(shí)例傳遞給它。

      Configure的裝載

      我們常使用Startup的Configure方法去配置中間件,默認(rèn)生成的Configure方法為我們添加了IApplicationBuilder和IWebHostEnvironment實(shí)例,但是其實(shí)Configure方法不僅僅可以傳遞這兩個(gè)參數(shù),它可以通過參數(shù)注入在IServiceCollection中注冊的所有服務(wù),究竟是如何實(shí)現(xiàn)的呢,接下來我們繼續(xù)探究UseStartup方法查找源碼查看想實(shí)現(xiàn)

      [點(diǎn)擊查看源碼],我們抽離出來核心實(shí)現(xiàn)如下

      //和ConfigureServices查找方式類似傳遞Startup實(shí)例和環(huán)境變量
      ConfigureBuilder configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
      services.Configure<GenericWebHostServiceOptions>(options =>
      {
      //通過查看GenericWebHostServiceOptions的源碼可知app其實(shí)就是IApplicationBuilder實(shí)例
      options.ConfigureApplication = app =>
      {
      startupError?.Throw();
      //執(zhí)行Startup.Configure,instance為Startup實(shí)例
      if (instance != null && configureBuilder != null)
      {
      //執(zhí)行Configure方法傳遞Startup實(shí)例和IApplicationBuilder實(shí)例
      configureBuilder.Build(instance)(app);
      }
      };
      });

      我們通過查看GenericWebHostServiceOptions的源碼可知ConfigureApplication屬性的類型為Action也就是說app參數(shù)其實(shí)就是IApplicationBuilder接口的實(shí)例。通過上面這段代碼可以看出,主要邏輯就是調(diào)用StartupLoader的FindConfigureDelegate方法,然后返回ConfigureBuilder建造類,然后構(gòu)建出Configure方法并執(zhí)行。首先我們來查看FindConfigureDelegate的邏輯實(shí)現(xiàn)

      [點(diǎn)擊查看源碼]

      internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
      {
      //通過startup類型和方法名為Configure或Configure+環(huán)境變量名稱的方法
      var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
      //用查找到的方法去初始化ConfigureBuilder
      return new ConfigureBuilder(configureMethod);
      }

      從這里我們可以看到FindConfigureDelegate方法也是調(diào)用的FindMethod方法,只是傳遞的方法名字符串為Configure或Configure+環(huán)境變量,關(guān)于FindMethod的方法實(shí)現(xiàn)我們在上面講解ConfigureServices方法的時(shí)候已經(jīng)非常詳細(xì)的說過了,這里就不過多的講解了??傊峭ㄟ^FindMethod去查找名為Configure的方法或名為Configure+環(huán)境變量的方法比如ConfigureDevelopment查找規(guī)則和ConfigureServices是完全一致的。但是Configure方法卻可以通過參數(shù)注入注冊到IServiceCollection中的服務(wù),答案我們同樣要在ConfigureBuilder類中去探尋

      [點(diǎn)擊查看源碼]

      internal class ConfigureBuilder
      {
      //構(gòu)造函數(shù)傳遞Configure的MethodInfo
      public ConfigureBuilder(MethodInfo configure)
      {
      MethodInfo = configure;
      }

      public MethodInfo MethodInfo { get; }
      //Build方法返回Action<IApplicationBuilder>委托
      public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder);
      //執(zhí)行邏輯
      private void Invoke(object instance, IApplicationBuilder builder)
      {
      //通過IApplicationBuilder的ApplicationServices獲取IServiceProvider實(shí)例創(chuàng)建一個(gè)作用域
      using (var scope = builder.ApplicationServices.CreateScope())
      {
      //獲取IServiceProvider實(shí)例
      var serviceProvider = scope.ServiceProvider;
      //獲取Configure的所有參數(shù)
      var parameterInfos = MethodInfo.GetParameters();
      var parameters = new object[parameterInfos.Length];
      for (var index = 0; index < parameterInfos.Length; index++)
      {
      var parameterInfo = parameterInfos[index];
      //如果方法參數(shù)為IApplicationBuilder類型則直接將傳遞過來的IApplicationBuilder賦值給它
      if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
      {
      parameters[index] = builder;
      }
      else
      {
      try
      {
      //根據(jù)方法的參數(shù)類型在serviceProvider中獲取具體實(shí)例賦值給對應(yīng)參數(shù)
      parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
      }
      catch (Exception ex)
      {
      //如果對應(yīng)的方法參數(shù)名稱,沒在serviceProvider中獲取到則直接拋出異常
      //變相的說明了Configure方法的參數(shù)必須是注冊在IServiceCollection中的
      }
      }
      }
      MethodInfo.InvokeWithoutWrappingExceptions(instance, parameters);
      }
      }
      }

      通過ConfigureBuilder類的實(shí)現(xiàn)邏輯,可以清晰的看到為何Configure方法參數(shù)可以注入任何在IServiceCollection中注冊的服務(wù)了。接下來我們總結(jié)一下Configure方法的初始化邏輯,首先在Startup中查找方法名為Configure或Configure+環(huán)境變量名稱(比如ConfigureDevelopment)的方法,然后查找IApplicationBuilder類型的參數(shù),如果找到則將程序中的IApplicationBuilder實(shí)例傳遞給它。至于為何Configure方法能夠通過參數(shù)注入任何在IServiceCollection中注冊的服務(wù),則是因?yàn)檠h(huán)Configure中的所有參數(shù)然后在IOC容器中獲取對應(yīng)實(shí)例賦值過來,Configure方法的參數(shù)一定得是在IServiceCollection注冊過的類型,否則會拋出異常。

      ConfigureContainer為何會被調(diào)用

      如果你在ASP.NET Core 3.1中使用過Autofac那么你對ConfigureContainer方法一定不陌生,它和ConfigureServices、Configure方法一樣的神奇,在幾乎沒有任何約束的情況下我們只需要定義ConfigureContainer方法并為方法傳遞一個(gè)ContainerBuilder參數(shù),那么這個(gè)方法就能順利的被調(diào)用了。這一切究竟是如何實(shí)現(xiàn)的呢,接下來我們繼續(xù)探究源碼,找到了如下的邏輯

      [點(diǎn)擊查看源碼]

      //根據(jù)規(guī)則查找最終返回ConfigureContainerBuilder實(shí)例
      var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName);
      if (configureContainerBuilder.MethodInfo != null)
      {
      //獲取容器類型比如如果是autofac則類型為ContainerBuilder
      var containerType = configureContainerBuilder.GetContainerType();
      // 存儲configureContainerBuilder實(shí)例
      _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder;
      //構(gòu)建一個(gè)Action<HostBuilderContext,containerType>類型的委托
      var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType);

      // 獲取此類型的私有ConfigureContainer方法,然后聲明該方法的泛型為容器類型,然后創(chuàng)建這個(gè)方法的委托
      var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance)
      .MakeGenericMethod(containerType)
      .CreateDelegate(actionType, this);

      // 等同于執(zhí)行_builder.ConfigureContainer<T>(ConfigureContainer),其中T為容器類型。
      //C onfigureContainer表示一個(gè)委托,即我們在Startup中定義的ConfigureContainer委托
      typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer))
      .MakeGenericMethod(containerType)
      .InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback });
      }

      繼續(xù)使用老配方,我們查看StartupLoader的FindConfigureContainerDelegate方法實(shí)現(xiàn)

      [點(diǎn)擊查看源碼]

      internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
      {
      //根據(jù)startupType和根據(jù)environmentName構(gòu)建的Configure{0}Services字符串先去查找返回類型為IServiceProvider的方法
      var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
      //用查找到的方法去初始化ConfigureContainerBuilder
      return new ConfigureContainerBuilder(configureMethod);
      }

      果然還是這個(gè)配方這個(gè)味道,廢話不多說直接查看ConfigureContainerBuilder源碼

      [點(diǎn)擊查看源碼]

      internal class ConfigureContainerBuilder
      {
      public ConfigureContainerBuilder(MethodInfo configureContainerMethod)
      {
      MethodInfo = configureContainerMethod;
      }
      public MethodInfo MethodInfo { get; }
      public Func<Action<object>, Action<object>> ConfigureContainerFilters { get; set; } = f => f;
      public Action<object> Build(object instance) => container => Invoke(instance, container);
      //查找容器類型,其實(shí)就是ConfigureContainer方法的的唯一參數(shù)
      public Type GetContainerType()
      {
      var parameters = MethodInfo.GetParameters();
      //ConfigureContainer方法只能包含一個(gè)參數(shù)
      if (parameters.Length != 1)
      {
      throw new InvalidOperationException($"The {MethodInfo.Name} method must take only one parameter.");
      }
      return parameters[0].ParameterType;
      }

      private void Invoke(object instance, object container)
      {
      ConfigureContainerFilters(StartupConfigureContainer)(container);
      void StartupConfigureContainer(object containerBuilder) => InvokeCore(instance, containerBuilder);
      }

      //根據(jù)傳遞的container對象執(zhí)行ConfigureContainer方法邏輯比如使用autofac時(shí)ConfigureContainer(ContainerBuilder)
      private void InvokeCore(object instance, object container)
      {
      if (MethodInfo == null)
      {
      return;
      }
      var arguments = new object[1] { container };
      MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments);
      }
      }

      果不其然千年老方下來還是那個(gè)味道,和ConfigureServices、Configure方法思路幾乎一致。這里需要注意的是GetContainerType獲取的容器類型是ConfigureContainer方法的唯一參數(shù)即容器類型,如果傳遞多個(gè)參數(shù)則直接拋出異常。其實(shí)Startup的ConfigureContainer方法經(jīng)過花里胡哨的一番操作之后,最終還是轉(zhuǎn)換成了雷士如下的操作方式,這個(gè)我們在上面代碼中構(gòu)建actionType的時(shí)候就可以看出,最終通過查找到的容器類型去完成注冊等相關(guān)操作,這里就不過多的講解了

      Host.CreateDefaultBuilder(args)
      .ConfigureContainer<ContainerBuilder>((context,container)=> {
      container.RegisterType<PersonService>().As<IPersonService>().InstancePerLifetimeScope();
      });

      總結(jié)

      本篇文章我們主要是圍繞著Startup是如何被初始化進(jìn)行講解的,分別講解了Startup是如何被實(shí)例化的,為何Startup的構(gòu)造函數(shù)只能傳遞IWebHostEnvironment、IHostEnvironment、IConfiguration類型的參數(shù),以及ConfigureServices、Configure、ConfigureContainer方法是如何查找到并被初始化調(diào)用的。其中雖然涉及到的代碼比較多,但是整體思路在閱讀源碼后還是比較清晰的。由于筆者文筆有限,可能許多地方描述的不夠清晰,亦或是本人能力有限理解的不夠透徹,不過本人在文章中都標(biāo)記了源碼所在位置的鏈接,如果有感興趣的同學(xué)可以自行點(diǎn)擊連接查看源碼。Startup類比較常用,如果能夠更深層次的了解其原理,對我們實(shí)際編程過程中會有很大的幫助,同時(shí)呼吁更多的小伙伴們深入閱讀了解.NET Core的源碼并分享出來。如有各位有疑問或者有了解的更透徹的,歡迎評論區(qū)提問或批評指導(dǎo)。

      來源:腳本之家

      鏈接:https://www.jb51.net/article/200322.htm

      申請創(chuàng)業(yè)報(bào)道,分享創(chuàng)業(yè)好點(diǎn)子。點(diǎn)擊此處,共同探討創(chuàng)業(yè)新機(jī)遇!

      相關(guān)標(biāo)簽
      asp.net
      net開發(fā)
      .net開發(fā)

      相關(guān)文章

      熱門排行

      信息推薦