亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時(shí)間:2020年11月23日 09:11:50   作者:yi念之間  
這篇文章主要介紹了深入探究ASP.NET Core Startup初始化問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

Startup類相信大家都比較熟悉,在我們使用ASP.NET Core開發(fā)過(guò)程中經(jīng)常用到的類,我們通常使用它進(jìn)行IOC服務(wù)注冊(cè),配置中間件信息等。雖然它不是必須的,但是將這些操作統(tǒng)一在Startup中做處理,會(huì)在實(shí)際開發(fā)中帶來(lái)許多方便。當(dāng)我們談起Startup類的時(shí)候你有沒有好奇過(guò)以下幾點(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注冊(cè)服務(wù)實(shí)例。
  • 在ASP.NET Core結(jié)合Autofac使用的時(shí)候?yàn)楹挝覀兲砑拥腃onfigureContainer方法會(huì)被調(diào)用。
  • 帶著以上幾點(diǎn)疑問,我們將在本篇文章中探索Startup的源碼,來(lái)了解Startup初始化過(guò)程到底為我們做了些什么。

Startup的另類指定方式

在日常編碼過(guò)程中,我們通常使用UseStartup的方式來(lái)引入Startup類。但是這并不是唯一的方式,還有一種方式是在配置節(jié)點(diǎn)中指定Startup所在的程序集來(lái)自動(dòng)查找Startup類,這個(gè)我們可以在GenericWebHostBuilder的構(gòu)造函數(shù)源碼中的找到相關(guān)代碼[點(diǎn)擊查看源碼👈]相信熟悉ASP.Net Core啟動(dòng)流程的同學(xué)對(duì)GenericWebHostBuilder這個(gè)類都比較了解。ConfigureWebHostDefaults方法中其實(shí)調(diào)用了ConfigureWebHost方法,ConfigureWebHost方法中實(shí)例化了GenericWebHostBuilder對(duì)象,啟動(dòng)流程不是咱們的重點(diǎn),所以這里只是簡(jiǎ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)
  {
    //此處省略代碼省略
  }
}

這里我們可以看出來(lái),我們需要配置StartupAssembly對(duì)應(yīng)的程序集,它可以通過(guò)StartupLoader的FindStartupType方法加載程序集中對(duì)應(yīng)的類。我們還可以看到它還傳遞了EnvironmentName環(huán)境變量,至于它起到了什么作用,我們繼續(xù)往下看。
首先我們需要找到webHostOptions.StartupAssembly是如何被初始化的,在WebHostOptions的構(gòu)造函數(shù)中我們找到了StartupAssembly初始化的地方[點(diǎn)擊查看源碼👈]

StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey];

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

public static readonly string StartupAssemblyKey = "startupAssembly";

也就是說(shuō)只要我們給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>();
        });

回到上面的思路,我們?cè)赟tartupLoader類中查看FindStartupType方法,來(lái)看下它是通過(guò)什么規(guī)則來(lái)查找Startup的[點(diǎn)擊查看源碼👈]精簡(jiǎ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;
}

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

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

相信對(duì)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è)操作的請(qǐng)先反思一下自己,然后在查閱微軟官方文檔。接下來(lái)我們就從源碼著手,來(lái)探究一下它到底是如何做到的。沿著上述的操作,繼續(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í)例化對(duì)象的方法,在日常編程中如果有需要可以使用這個(gè)類。上面的ActivatorUtilities的CreateInstance方法的功能就是根據(jù)傳遞IServiceProvider類型的對(duì)象去實(shí)例化指定的類型對(duì)象,我們這里的類型就是startupType。它的使用場(chǎng)景就是,如果某個(gè)類型需要用過(guò)有參構(gòu)造函數(shù)去實(shí)例化,而構(gòu)造函數(shù)的參數(shù)可以來(lái)自于IServiceProvider的實(shí)例,那么使用這個(gè)方法就在合適不過(guò)了。上面的代碼傳遞的IServiceProvider的實(shí)例是HostServiceProvider對(duì)象,接下來(lái)我們找到它的實(shí)現(xiàn)源碼[點(diǎn)擊查看源碼👈]代碼并不多我們就全部粘貼出來(lái)

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

  public object GetService(Type serviceType)
  {
    // 通過(guò)這里我們就比較清晰的看出,只有滿足這幾種情況下才能返回具體的實(shí)例,其他的都會(huì)返回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;
  }
}

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

ConfigureServices的裝載

接下來(lái)我們就來(lái)在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對(duì)象即IServiceCollection對(duì)象調(diào)用ConfigureServices方法
configureServices(services);

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

通過(guò)這里的源碼我們可以看到在startupType類型里去查找名字為environmentName構(gòu)建的Configure{0}Services的方法信息,然后根據(jù)查找的方法信息即MethodInfo對(duì)象去構(gòu)建ConfigureServicesBuilder實(shí)例。接下里我們就來(lái)查詢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;
}

通過(guò)FindMethod方法我們可以得到幾個(gè)結(jié)論,首先ConfigureServices方法的名稱可以是包含環(huán)境變量的名稱比如(ConfigureDevelopmentServices),其次方法可以為共有的靜態(tài)或非靜態(tài)方法。FindMethod方法是真正執(zhí)行查找的邏輯所在,如果找到相關(guān)方法則返回MethodInfo。FindMethod查找的方法名稱是通過(guò)methodName參數(shù)傳遞進(jìn)來(lái)的,我們標(biāo)注的注釋代碼都是直接寫死了ConfigureServices方法,只是為了便于說(shuō)明理解,但其實(shí)FindMethod是通用方法,接下來(lái)我們要講解的內(nèi)容還會(huì)涉及到這個(gè)方法,到時(shí)候關(guān)于這個(gè)代碼的邏輯我們就不會(huì)在進(jìn)行說(shuō)明了,因?yàn)槭峭粋€(gè)方法,希望大家能注意到這一點(diǎn)。
通過(guò)上面的相關(guān)方法,我們了解到了是通過(guò)什么樣的規(guī)則去查找到ConfigureServices的方法信息的,我們也看到了ConfigureServicesBuilder正是通過(guò)查找到的MethodInfo去構(gòu)造實(shí)例的,接下來(lái)我們就來(lái)查看下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類型則直接拋出異常
    // 也就是說(shuō)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)于通過(guò)什么樣的邏輯查找并執(zhí)行ConfigureServices方法的邏輯就非常清晰了。首先是查找ConfigureServices方法,即包含環(huán)境變量的ConfigureServices方法名稱比如(ConfigureDevelopmentServices)或名為ConfigureServices的方法,返回的是ConfigureServicesBuilder對(duì)象。然后執(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ù),它可以通過(guò)參數(shù)注入在IServiceCollection中注冊(cè)的所有服務(wù),究竟是如何實(shí)現(xiàn)的呢,接下來(lái)我們繼續(xù)探究UseStartup方法查找源碼查看想實(shí)現(xiàn)
[點(diǎn)擊查看源碼👈],我們抽離出來(lái)核心實(shí)現(xiàn)如下

//和ConfigureServices查找方式類似傳遞Startup實(shí)例和環(huán)境變量
ConfigureBuilder configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
services.Configure<GenericWebHostServiceOptions>(options =>
{
  //通過(guò)查看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);
    }
  };
});

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

internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
  //通過(guò)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)我們?cè)谏厦嬷v解ConfigureServices方法的時(shí)候已經(jīng)非常詳細(xì)的說(shuō)過(guò)了,這里就不過(guò)多的講解了??傊峭ㄟ^(guò)FindMethod去查找名為Configure的方法或名為Configure+環(huán)境變量的方法比如ConfigureDevelopment查找規(guī)則和ConfigureServices是完全一致的。但是Configure方法卻可以通過(guò)參數(shù)注入注冊(cè)到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)
  {
    //通過(guò)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類型則直接將傳遞過(guò)來(lái)的IApplicationBuilder賦值給它
        if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
        {
          parameters[index] = builder;
        }
        else
        {
          try
          {
            //根據(jù)方法的參數(shù)類型在serviceProvider中獲取具體實(shí)例賦值給對(duì)應(yīng)參數(shù)
            parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
          }
          catch (Exception ex)
          {
            //如果對(duì)應(yīng)的方法參數(shù)名稱,沒在serviceProvider中獲取到則直接拋出異常
            //變相的說(shuō)明了Configure方法的參數(shù)必須是注冊(cè)在IServiceCollection中的
          }
        }
      }
      MethodInfo.InvokeWithoutWrappingExceptions(instance, parameters);
    }
  }
}

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

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

如果你在ASP.NET Core 3.1中使用過(guò)Autofac那么你對(duì)ConfigureContainer方法一定不陌生,它和ConfigureServices、Configure方法一樣的神奇,在幾乎沒有任何約束的情況下我們只需要定義ConfigureContainer方法并為方法傳遞一個(gè)ContainerBuilder參數(shù),那么這個(gè)方法就能順利的被調(diào)用了。這一切究竟是如何實(shí)現(xiàn)的呢,接下來(lái)我們繼續(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();
  // 存儲(chǔ)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è)委托,即我們?cè)赟tartup中定義的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è)味道,廢話不多說(shuō)直接查看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對(duì)象執(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);
  }
}

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

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)用的。其中雖然涉及到的代碼比較多,但是整體思路在閱讀源碼后還是比較清晰的。由于筆者文筆有限,可能許多地方描述的不夠清晰,亦或是本人能力有限理解的不夠透徹,不過(guò)本人在文章中都標(biāo)記了源碼所在位置的鏈接,如果有感興趣的同學(xué)可以自行點(diǎn)擊連接查看源碼。Startup類比較常用,如果能夠更深層次的了解其原理,對(duì)我們實(shí)際編程過(guò)程中會(huì)有很大的幫助,同時(shí)呼吁更多的小伙伴們深入閱讀了解.NET Core的源碼并分享出來(lái)。如有各位有疑問或者有了解的更透徹的,歡迎評(píng)論區(qū)提問或批評(píng)指導(dǎo)。

相關(guān)文章

  • 獲取遠(yuǎn)程網(wǎng)頁(yè)的內(nèi)容之二(downmoon原創(chuàng))

    獲取遠(yuǎn)程網(wǎng)頁(yè)的內(nèi)容之二(downmoon原創(chuàng))

    獲取遠(yuǎn)程網(wǎng)頁(yè)的內(nèi)容之二(downmoon原創(chuàng))...
    2007-03-03
  • Asp.net MVC下使用Bundle合并、壓縮js與css文件詳解

    Asp.net MVC下使用Bundle合并、壓縮js與css文件詳解

    在web優(yōu)化中有一種手段,壓縮js,css文件,減少文件大小,合并js,css文件減少請(qǐng)求次數(shù)。asp.net mvc中為我們提供一種使用c#代碼壓縮合并js和css這類靜態(tài)文件的方法。這篇文章主要介紹了在Asp.net MVC下使用Bundle合并、壓縮js與css文件的方法,需要的朋友可以參考下。
    2017-03-03
  • C# 實(shí)現(xiàn)抓取網(wǎng)站頁(yè)面內(nèi)容的實(shí)例方法

    C# 實(shí)現(xiàn)抓取網(wǎng)站頁(yè)面內(nèi)容的實(shí)例方法

    這篇文章介紹了C# 實(shí)現(xiàn)抓取網(wǎng)站頁(yè)面內(nèi)容的實(shí)例方法,有需要的朋友可以參考一下
    2013-08-08
  • 詳解ASP.NET七大身份驗(yàn)證方式以及解決方案

    詳解ASP.NET七大身份驗(yàn)證方式以及解決方案

    這篇文章主要為大家詳細(xì)介紹了ASP.NET七大身份驗(yàn)證方式以及解決方案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2015-08-08
  • asp.net 實(shí)現(xiàn)下拉框只讀功能

    asp.net 實(shí)現(xiàn)下拉框只讀功能

    下拉框只讀此功能并不是默認(rèn)的,需要手動(dòng)操作下,本文介紹javascript與asp.net實(shí)現(xiàn)下拉框只讀功能,需要了解的朋友可以參考一下
    2012-12-12
  • .Net Core和jexus配置HTTPS服務(wù)方法

    .Net Core和jexus配置HTTPS服務(wù)方法

    下面小編就為大家分享一篇.Net Core和jexus配置HTTPS服務(wù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • ASP.NET?MVC使用jQuery?ui的progressbar實(shí)現(xiàn)進(jìn)度條

    ASP.NET?MVC使用jQuery?ui的progressbar實(shí)現(xiàn)進(jìn)度條

    這篇文章介紹了ASP.NET?MVC使用jQuery?ui的progressbar實(shí)現(xiàn)進(jìn)度條的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • .net core使用MD5加密解密字符串

    .net core使用MD5加密解密字符串

    這篇文章主要為大家詳細(xì)介紹了.net core使用MD5加密解密字符串,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • 關(guān)于C# if語(yǔ)句中并列條件的執(zhí)行

    關(guān)于C# if語(yǔ)句中并列條件的執(zhí)行

    我們知道,當(dāng)兩個(gè)條件進(jìn)行邏輯與操作的時(shí)候,其中任何一個(gè)條件為假,則表達(dá)式的結(jié)果為假。所以,遇到(A 且 B)這種表達(dá)式,如果A為假的話,B是不是真假都無(wú)所謂了,當(dāng)遇到一個(gè)假條件的時(shí)候,程序也就沒有必要去額外的判斷剩下的東西了
    2012-02-02
  • 獲取DataList控件的主鍵和索引實(shí)用圖解

    獲取DataList控件的主鍵和索引實(shí)用圖解

    一是在DataList控件添加一個(gè)DataKeyField,以便獲取到它的主鍵值,另外還添加了兩個(gè)銨鈕及一個(gè)Label標(biāo)答,用來(lái)顯示選擇結(jié)果,真正將來(lái)你也許用不上標(biāo)簽,因?yàn)楂@取到結(jié)果之后,就可以進(jìn)行你想的要事情了
    2013-01-01

最新評(píng)論