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

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

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

  域名預(yù)訂/競價,好“米”不錯過

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

前言

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

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

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

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

ConfigureServices方法為何只能傳遞IServiceCollection實例。

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

在ASP.NET Core結(jié)合Autofac使用的時候為何我們添加的ConfigureContainer方法會被調(diào)用。

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

Startup的另類指定方式

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

//判斷是否配置了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初始化的地方[點擊查看源碼]

StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey];

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

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的[點擊查看源碼]精簡之后的代碼大致如下

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ī)則的Startup類的,基本上可以理解為先去查找名稱為Startup+環(huán)境變量的類,如果找不到則繼續(xù)查找名稱為Startup的類,最終會返回Startup的類型傳遞給UseStartup方法。其實我們最常使用的UseStartup()方法最終也是轉(zhuǎn)換成UseStartup(typeof(T))的方式,所以最終這兩種方式走到了相同的地方,接下來我們步入正題,來一起探究一下Starup究竟是如何被初始化的。

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

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

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

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

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

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

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

ConfigureServices的裝載

接下來我們就來在UseStartup方法里繼續(xù)查看是如何查找并執(zhí)行ConfigureServices方法的,繼續(xù)查看找到如下實現(xià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實例,然后構(gòu)建ConfigureServicesBuilder實例返回ConfigureServices方法的委托,最后傳遞IServiceCollection對象執(zhí)行委托方法。接下來我們就來查看具體實現(xiàn)源碼。

我們在StartupLoader類中找到了FindConfigureServicesDelegate方法的相關(guān)實現(xià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實例
return new ConfigureServicesBuilder(servicesMethod);
}

通過這里的源碼我們可以看到在startupType類型里去查找名字為environmentName構(gòu)建的Configure{0}Services的方法信息,然后根據(jù)查找的方法信息即MethodInfo對象去構(gòu)建ConfigureServicesBuilder實例。接下里我們就來查詢FindMethod方法的實現(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)
{
//找打多個滿足規(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();
//如果存在多個則同樣拋出異常
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方法我們可以得到幾個結(jié)論,首先ConfigureServices方法的名稱可以是包含環(huán)境變量的名稱比如(ConfigureDevelopmentServices),其次方法可以為共有的靜態(tài)或非靜態(tài)方法。FindMethod方法是真正執(zhí)行查找的邏輯所在,如果找到相關(guān)方法則返回MethodInfo。FindMethod查找的方法名稱是通過methodName參數(shù)傳遞進(jìn)來的,我們標(biāo)注的注釋代碼都是直接寫死了ConfigureServices方法,只是為了便于說明理解,但其實FindMethod是通用方法,接下來我們要講解的內(nèi)容還會涉及到這個方法,到時候關(guān)于這個代碼的邏輯我們就不會在進(jìn)行說明了,因為是同一個方法,希望大家能注意到這一點。

通過上面的相關(guān)方法,我們了解到了是通過什么樣的規(guī)則去查找到ConfigureServices的方法信息的,我們也看到了ConfigureServicesBuilder正是通過查找到的MethodInfo去構(gòu)造實例的,接下來我們就來查看下ConfigureServicesBuilder的實現(xià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í)行這個委托需傳遞services即IServiceCollections實例返回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方法包含多個參數(shù)或方法參數(shù)類型不是IServiceCollection類型則直接拋出異常
// 也就是說ConfigureServices只能包含一個參數(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ù)
var arguments = new object[MethodInfo.GetParameters().Length];
if (parameters.Length > 0)
{
arguments[0] = services;
}
// 執(zhí)行返回IServiceProvider實例
return MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments) as IServiceProvider;
}
}

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

Configure的裝載

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

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

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

我們通過查看GenericWebHostServiceOptions的源碼可知ConfigureApplication屬性的類型為Action也就是說app參數(shù)其實就是IApplicationBuilder接口的實例。通過上面這段代碼可以看出,主要邏輯就是調(diào)用StartupLoader的FindConfigureDelegate方法,然后返回ConfigureBuilder建造類,然后構(gòu)建出Configure方法并執(zhí)行。首先我們來查看FindConfigureDelegate的邏輯實現(xià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的方法實現(xiàn)我們在上面講解ConfigureServices方法的時候已經(jīng)非常詳細(xì)的說過了,這里就不過多的講解了??傊峭ㄟ^FindMethod去查找名為Configure的方法或名為Configure+環(huán)境變量的方法比如ConfigureDevelopment查找規(guī)則和ConfigureServices是完全一致的。但是Configure方法卻可以通過參數(shù)注入注冊到IServiceCollection中的服務(wù),答案我們同樣要在ConfigureBuilder類中去探尋

[點擊查看源碼]

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實例創(chuàng)建一個作用域
using (var scope = builder.ApplicationServices.CreateScope())
{
//獲取IServiceProvider實例
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中獲取具體實例賦值給對應(yīng)參數(shù)
parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
}
catch (Exception ex)
{
//如果對應(yīng)的方法參數(shù)名稱,沒在serviceProvider中獲取到則直接拋出異常
//變相的說明了Configure方法的參數(shù)必須是注冊在IServiceCollection中的
}
}
}
MethodInfo.InvokeWithoutWrappingExceptions(instance, parameters);
}
}
}

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

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

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

[點擊查看源碼]

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

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

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

繼續(xù)使用老配方,我們查看StartupLoader的FindConfigureContainerDelegate方法實現(xià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);
}

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

[點擊查看源碼]

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);
//查找容器類型,其實就是ConfigureContainer方法的的唯一參數(shù)
public Type GetContainerType()
{
var parameters = MethodInfo.GetParameters();
//ConfigureContainer方法只能包含一個參數(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時ConfigureContainer(ContainerBuilder)
private void InvokeCore(object instance, object container)
{
if (MethodInfo == null)
{
return;
}
var arguments = new object[1] { container };
MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments);
}
}

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

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

總結(jié)

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

來源:腳本之家

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

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

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

相關(guān)文章

熱門排行

信息推薦