南通颐猩文化传播有限公司

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

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

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

  域名預(yù)訂/競(jìng)價(jià),好“米”不錯(cuò)過

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

前言

Startup類相信大家都比較熟悉,在我們使用ASP.NET Core開發(fā)過程中經(jīng)常用到的類,我們通常使用它進(jìn)行IOC服務(wù)注冊(cè),配置中間件信息等。雖然它不是必須的,但是將這些操作統(tǒng)一在Startup中做處理,會(huì)在實(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注冊(cè)服務(wù)實(shí)例。

在ASP.NET Core結(jié)合Autofac使用的時(shí)候?yàn)楹挝覀兲砑拥腃onfigureContainer方法會(huì)被調(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é)對(duì)GenericWebHostBuilder這個(gè)類都比較了解。ConfigureWebHostDefaults方法中其實(shí)調(diào)用了ConfigureWebHost方法,ConfigureWebHost方法中實(shí)例化了GenericWebHostBuilder對(duì)象,啟動(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對(duì)應(yīng)的程序集,它可以通過StartupLoader的FindStartupType方法加載程序集中對(duì)應(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>();
});

回到上面的思路,我們?cè)赟tartupLoader類中查看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的類,最終會(huì)返回Startup的類型傳遞給UseStartup方法。其實(shí)我們最常使用的UseStartup()方法最終也是轉(zhuǎn)換成UseStartup(typeof(T))的方式,所以最終這兩種方式走到了相同的地方,接下來我們步入正題,來一起探究一下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)先反思一下自己,然后在查閱微軟官方文檔。接下來我們就從源碼著手,來探究一下它到底是如何做到的。沿著上述的操作,繼續(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è)類型需要用過有參構(gòu)造函數(shù)去實(shí)例化,而構(gòu)造函數(shù)的參數(shù)可以來自于IServiceProvider的實(shí)例,那么使用這個(gè)方法就在合適不過了。上面的代碼傳遞的IServiceProvider的實(shí)例是HostServiceProvider對(duì)象,接下來我們找到它的實(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í)例,其他的都會(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;
}
}

通過這個(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í)候,通過構(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對(duì)象即IServiceCollection對(duì)象調(diào)用ConfigureServices方法
configureServices(services);

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

通過這里的源碼我們可以看到在startupType類型里去查找名字為environmentName構(gòu)建的Configure{0}Services的方法信息,然后根據(jù)查找的方法信息即MethodInfo對(duì)象去構(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)容還會(huì)涉及到這個(gè)方法,到時(shí)候關(guān)于這個(gè)代碼的邏輯我們就不會(huì)在進(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對(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ù),它可以通過參數(shù)注入在IServiceCollection中注冊(cè)的所有服務(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)我們?cè)谏厦嬷v解ConfigureServices方法的時(shí)候已經(jīng)非常詳細(xì)的說過了,這里就不過多的講解了??傊峭ㄟ^FindMethod去查找名為Configure的方法或名為Configure+環(huán)境變量的方法比如ConfigureDevelopment查找規(guī)則和ConfigureServices是完全一致的。但是Configure方法卻可以通過參數(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)
{
//通過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í)例賦值給對(duì)應(yīng)參數(shù)
parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
}
catch (Exception ex)
{
//如果對(duì)應(yīng)的方法參數(shù)名稱,沒在serviceProvider中獲取到則直接拋出異常
//變相的說明了Configure方法的參數(shù)必須是注冊(cè)在IServiceCollection中的
}
}
}
MethodInfo.InvokeWithoutWrappingExceptions(instance, parameters);
}
}
}

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

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

如果你在ASP.NET Core 3.1中使用過Autofac那么你對(duì)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();
// 存儲(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è)味道,廢話不多說直接查看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);
}
}

果不其然千年老方下來還是那個(gè)味道,和ConfigureServices、Configure方法思路幾乎一致。這里需要注意的是GetContainerType獲取的容器類型是ConfigureContainer方法的唯一參數(shù)即容器類型,如果傳遞多個(gè)參數(shù)則直接拋出異常。其實(shí)Startup的ConfigureContainer方法經(jīng)過花里胡哨的一番操作之后,最終還是轉(zhuǎn)換成了雷士如下的操作方式,這個(gè)我們?cè)谏厦娲a中構(gòu)建actionType的時(shí)候就可以看出,最終通過查找到的容器類型去完成注冊(cè)等相關(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類比較常用,如果能夠更深層次的了解其原理,對(duì)我們實(shí)際編程過程中會(huì)有很大的幫助,同時(shí)呼吁更多的小伙伴們深入閱讀了解.NET Core的源碼并分享出來。如有各位有疑問或者有了解的更透徹的,歡迎評(píng)論區(qū)提問或批評(píng)指導(dǎo)。

來源:腳本之家

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

申請(qǐng)創(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)文章

熱門排行

信息推薦