開篇
之前寫過Asp.net core 中的Startup類是啥?和dotnet core 打造干凈的Web API服務(wù). 相信讀過的朋友可以對asp.net core的啟動機(jī)制有一定的理解. 可我們終究不能滿足于此, 對于一些沒有充足時間通讀源碼卻對源碼有深深渴望的朋友, 我希望通過這個系列幫助大家用很短的時間對asp.net core的源碼有較為深入的理解.
瀏覽指南
作為這個系列的開篇, 希望為此系列定個調(diào)調(diào), 也方便您瀏覽和查閱. Asp.net core 的源碼地址為https://github.com/aspnet 如果您有興趣可以自行查閱.
身為一個老碼農(nóng), 我習(xí)慣讀代碼的方式是先對代碼整體有一個概覽, 根據(jù)各個模塊和類的命名對程序結(jié)構(gòu)有一個大體了解. 之后從程序入口開始, 層層推進(jìn)(哪里不懂讀哪里) . 最后如果實(shí)在理解有困難就采取各種方式Debug.
另外, asp.net core的源碼我還沒有通讀, 只是比較膚淺的有一些理解, 也是通過寫文章, 促使自己更加深入的了解這個框架(每天讓游戲消磨我寶貴的時間深感罪惡, 我要洗心革面重新做人). 利人利己, 這個事我做!
提醒一點(diǎn), 這個系列我自己也不清楚能不能完成, 讀到哪寫到哪, 寫到哪算到哪, 如果給您帶跑偏概不負(fù)責(zé)啊.
從WebHostBuilder入手
當(dāng)你新建一個asp.net core的工程, 項目模板為您生成的代碼結(jié)構(gòu)大概是這樣的
很俗套的MVC. 你會發(fā)現(xiàn)Program.cs文件中有這樣一段代碼
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.UseUrls("http://*:5000")
.Build();
host.Run();
}
對的, 就是程序入口的Main函數(shù), 我們就從這里看起.
這段代碼的意圖很明顯, 就是要生成的Http Server的宿主. WebHostBuilder負(fù)責(zé)建造這個宿主. 它被包含于Hosting這個工程中, 我們根據(jù)這個鏈接可以得到相關(guān)的源碼.
打開源碼, 我們看到WebHostBuilder繼承于IWebHostBuilder接口, IWebHostBuilder的結(jié)構(gòu)是這樣的
能夠看出Build函數(shù)是最終生成宿主的方法, 除了Build函數(shù), 大部分的函數(shù)都返回IWebHostBuilder, 也就是返回它本身, 這也意味著這些函數(shù)是在對將要生成的宿主進(jìn)行配置. 我們轉(zhuǎn)到具體的實(shí)現(xiàn)邏輯來看看配置項都有哪些.
public IWebHostBuilder UseSetting(string key, string value)
{
_config[key] = value;
return this;
}
先來看這個UseSetting, 代碼很簡單, 而且根據(jù)命名可以知道這個函數(shù)就是進(jìn)行配置的, 其中的_config是IConfiguration類型, 是一個配置項的集合. 我們可以在WebHostBuilder的構(gòu)造函數(shù)中找到_config的實(shí)例化代碼
其中AddEnvironmentVariables函數(shù)的意圖是將環(huán)境變量中的配置加入到這個服務(wù)的配置中來.
public IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_createLoggerFactoryDelegate = _ => loggerFactory;
return this;
}
這個函數(shù)很好理解, 用于配置一個Logger工廠的實(shí)例.
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
{
if (configureServices == null)
{
throw new ArgumentNullException(nameof(configureServices));
}
_configureServicesDelegates.Add(configureServices);
return this;
}
這個ConfigureServices函數(shù)看代碼的意思是把一個匿名函數(shù)加入到一個匿名函數(shù)集合, 也就是_configureServicesDelegates中, 那么問題來了, 這個匿名函數(shù)是干啥的? 我們帶著問題繼續(xù)往下看.
public IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging)
{
if (configureLogging == null)
{
throw new ArgumentNullException(nameof(configureLogging));
}
_configureLoggingDelegates.Add(configureLogging);
return this;
}
這個函數(shù)也是把一個匿名函數(shù)加入到一個匿名函數(shù)的集合_configureLoggingDelegates, 類似的函數(shù)還有ConfigureConfiguration(里面有一個集合_configureConfigurationBuilderDelegates), 我們后面看看它的用途.
最后, 最重要的Build函數(shù)是我們也要重點(diǎn)了解的, 它里面定義了具體的實(shí)現(xiàn)邏輯, 代碼比較長, 我們分步來看
if (_webHostBuilt)
{
throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
}
_webHostBuilt = true;
這段很好理解, 對于一個Builder有一個宿主實(shí)例. 后面這幾句則是Build函數(shù)中的核心邏輯, 也是WebHostBuilder這個類的核心了.
var hostingServices = BuildCommonServices(out var hostingStartupErrors);
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();
AddApplicationServices(applicationServices, hostingServiceProvider);
先來看這個函數(shù)BuildCommonServices, 這是一個WebHostBuilder類的私有函數(shù), 從命名我們可以看出, 它是要把所有Common的Services配置到將來的宿主中, 我們來看看實(shí)現(xiàn)
_options = new WebHostOptions(_config);
這句把之前配置好的_config對象轉(zhuǎn)換為了WebHostOptions對象, 萬變不離其宗, 它還是個配置表. 后面一段代碼還是跟配置相關(guān), 包括環(huán)境變量, 根目錄, 應(yīng)用名等等.
var appEnvironment = PlatformServices.Default.Application;
var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, appEnvironment.ApplicationBasePath);
var applicationName = _options.ApplicationName ?? appEnvironment.ApplicationName;
var hostingContext = new WebHostBuilderContext
{
Configuration = _config
};
后面兩句初始化了_hostingEnvironment對象并把它指給hostingContext對象
// Initialize the hosting environment
_hostingEnvironment.Initialize(applicationName, contentRootPath, _options);
hostingContext.HostingEnvironment = _hostingEnvironment;
_hostingEnvironment和hostingContext對象在后面也都有用到.
var services = new ServiceCollection();
services.AddSingleton(_hostingEnvironment);
上面兩句代碼定義了一個依賴注入的服務(wù)容器, 并把_hostingEnvironment對象作為一個服務(wù)注冊到容器中. 用于在其他地方訪問環(huán)境的一些信息(包括應(yīng)用名, 環(huán)境名, 根路徑等).
var builder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddInMemoryCollection(_config.AsEnumerable());
foreach (var configureConfiguration in _configureConfigurationBuilderDelegates)
{
configureConfiguration(hostingContext, builder);
}
var configuration = builder.Build();
services.AddSingleton<IConfiguration>(configuration);
hostingContext.Configuration = configuration;
這段代碼主要生成了configuration對象, 并把它加入到依賴注入的容器中. 其中用到了_configureConfigurationBuilderDelegates這個匿名函數(shù)集合的對象. 一個foreach把_configureConfigurationBuilderDelegates中的所有匿名函數(shù)執(zhí)行了一遍, 使用hostingContext和builder作為參數(shù). 意圖很明顯, 用戶自定義的一些作用于hostingContext和builder的匿名函數(shù)在這里被執(zhí)行了, 起到了應(yīng)有的作用. 說白了就是為了修改,刪除或新增了某些配置項.
// The configured ILoggerFactory is added as a singleton here. AddLogging below will not add an additional one.
var loggerFactory = _createLoggerFactoryDelegate?.Invoke(hostingContext) ?? new LoggerFactory(configuration.GetSection("Logging"));
services.AddSingleton(loggerFactory);
hostingContext.LoggerFactory = loggerFactory;
上面這段創(chuàng)建了一個loggerFactory實(shí)例, 并把它加入到依賴注入的容器中.
var exceptions = new List<Exception>();
// Execute the hosting startup assemblies
foreach (var assemblyName in _options.HostingStartupAssemblies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
{
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
hostingStartup.Configure(this);
}
}
catch (Exception ex)
{
// Capture any errors that happen during startup
exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
}
}
if (exceptions.Count > 0)
{
hostingStartupErrors = new AggregateException(exceptions);
// Throw directly if we're not capturing startup errors
if (!_options.CaptureStartupErrors)
{
throw hostingStartupErrors;
}
}
這段說明用戶可以在配置文件中配置啟動程序集對宿主進(jìn)行一定的配置, 沒有特別需要的話這塊估計很少會用到.
// Kept for back-compat, will remove once ConfigureLogging is removed.
foreach (var configureLogging in _configureLoggingDelegates)
{
configureLogging(loggerFactory);
}
這段明顯就是通過一些自定義的匿名函數(shù)對Logger工廠進(jìn)行配置. 具體的大家實(shí)踐一下吧, 后面計劃單獨(dú)一篇講講Logger的機(jī)制.
// Kept for back-compat, will remove once ConfigureLogging is removed.
foreach (var configureLogging in _configureLoggingDelegates)
{
configureLogging(loggerFactory);
}
//This is required to add ILogger of T.
services.AddLogging();
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(listener);
services.AddSingleton<DiagnosticSource>(listener);
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.AddOptions();
// Conjure up a RequestServices
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
// Ensure object pooling is available everywhere.
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
if (!string.IsNullOrEmpty(_options.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup), startupType);
}
else
{
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
return new ConventionBasedStartup(methods);
});
}
}
catch (Exception ex)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.AddSingleton<IStartup>(_ =>
{
capture.Throw();
return null;
});
}
}
這一段又注入了很多服務(wù), 不再一一細(xì)講, 后面用到的時候我們再回過頭來看. 其中有一點(diǎn)比較特殊的是_options.StartupAssembly, 說明對于Startup也可以在配置文件中進(jìn)行配置, 則系統(tǒng)回自動反射為對象, 并把此對象作為一個依賴注入的服務(wù).
foreach (var configureServices in _configureServicesDelegates)
{
configureServices(services);
}
return services;
這段能回答上面的問題了, _configureServicesDelegates用戶保存一些匿名函數(shù), 這些匿名函數(shù)將在Build宿主的時候被執(zhí)行, 并且這些函數(shù)有一個共同的參數(shù)類型IServiceCollection, 很顯然, 這是一個依賴注入的對外暴露的接口. 最后把services這個對象, 這個依賴注入的容器返回.
我們回到Build函數(shù), 后面的操作很簡單, 將返回的services對象克隆一份賦值給applicationServices, 并調(diào)用了AddApplicationServices函數(shù).
private void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider)
{
var loggerFactory = hostingServiceProvider.GetService<ILoggerFactory>();
services.Replace(ServiceDescriptor.Singleton(typeof(ILoggerFactory), loggerFactory));
var listener = hostingServiceProvider.GetService<DiagnosticListener>();
services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticListener), listener));
services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticSource), listener));
}
這個AddApplicationServices函數(shù)的邏輯也很簡單, 將幾個服務(wù)的生命周期轉(zhuǎn)換為單例模式.
var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors);
host.Initialize();
return host;
最終用上面得出的幾個對象創(chuàng)建了一個WebHost對象.
總結(jié)
總的看來WebHostBuilder做了兩件事:
1. 定義了一些WebHost的配置項
2. 創(chuàng)建依賴注入的容器, 并注入一些service
作為開篇, 川酷盡量做到嚴(yán)謹(jǐn)和詳細(xì), 如果有不明白的地方歡迎討論. 也歡迎您的批評指正. 下一篇計劃寫一篇關(guān)于WebHost的內(nèi)容, 謝謝您的關(guān)注!
--------------------------華麗麗的分割-----------------------------------
愿dotnet社區(qū)日益強(qiáng)大, 愿dotnet生態(tài)日趨完善. dotnet core技術(shù)交流群歡迎你, 掃描下面二維碼進(jìn)群.