前言
本篇文章着重講一下在.Net中Host主機的構建過程,依舊延續之前文章的思路,着重講解其源碼,如果有不知道有哪些用法的同學可以點擊這里,廢話不多說,咱們直接進入正題
Host構建過程
下圖是我自己整理的Host構建過程以及里面包含的知識點我都以鏈接的形式放上來,大家可以看下圖,大概了解下過程(由於知識點過多,所以只能分上下兩張圖了😊):


圖中標識的相關知識點連接如下(ps:與編號對應):
- 1 環境變量 點擊這里
- 2 命令行參數 點擊這里
- 3 默認配置 點擊這里
- 4 用戶機密數據 點擊這里
- 5 默認logging 點擊這里
- 6 使用承載啟動程序集 點擊這里
- 7 自定義配置 點擊這里
- 8 依賴注入 點擊這里
- 9 應用啟動后台任務 點擊這里
- 10 中間件構建 點擊這里
- 11 線程spinwait 點擊這里
以上就是筆者在源碼閱讀階段,其總結的自我感覺重要的知識點在微軟文檔中的對應位置。
源碼解析
這部分筆者根據上圖中的四大塊分別進行源碼解析,可能篇幅比較長,其主要是對源代碼增加了自己理解的注釋,所以讀者在閱讀的過程中,要多注意源碼中的注釋(ps:展示出的代碼不是全部代碼,而只是重要代碼哦,每個小節總結的點都是按照代碼順序解釋)
初始化默認配置ConfigDefaultBuilder
public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args)
{
//設置程序運行路徑
builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
//添加獲取環境變量的前綴
config.AddEnvironmentVariables(prefix: "DOTNET_");
//添加命令行參數
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
//宿主機環境信息
IHostEnvironment env = hostingContext.HostingEnvironment;
//是否在文件改變時重新加載,默認是True
bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext);
//默認添加的配置文件(格外添加以環境變量為名稱的文件)
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
//如果是開發環境,並且應用程序的應用名稱不是空字符串,則加載用戶機密,默認true(主要是為了不同開發人員的配置不同)
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly is not null)
{
config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
}
}
//這里再次執行是為了讓環境變量和命令行參數的配置優先級提高(后加載的key/value獲取時優先級最高)
//添加其他環境變量
config.AddEnvironmentVariables();
//添加命令行參數
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
//判斷操作系統
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
//添加過濾規則,捕獲warning日志
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
//獲取Logging配置
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
//添加輸出到控制台
logging.AddConsole();
//添加將debug日志輸出到控制台
logging.AddDebug();
//添加寫入的事件源
logging.AddEventSourceLogger();
if (isWindows)
{
//添加事件日志
logging.AddEventLog();
}
//添加鏈路追蹤選項
logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
//依賴注入相關校驗
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
源碼總結:
- 設置程序執行路徑以及獲取環境變量和加載命令行參數。
- 根據環境變量加載appsettings.json,加載用戶機密數據(僅開發環境)。
- 接着又加載環境變量和命令行參數(這里為什么又加載了一次呢?是因為這它們執行的順序是不一樣的,而后加載的會覆蓋前面加載的Key/Value,前面加載主要是確定當前運行的環境變量以及用戶自定義的命令行參數,后面是為確保通過key獲取value的時候能夠獲取到准確的值)。
- 接下來就主要是配置默認Log,如果是開發環境,依賴注入相關的配置默認開啟(驗證scope是否被用於singleton,驗證是否在調用期間就創建所有服務至緩存)。
初始化主機啟動配置ConfigureWebHostDefaults
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
{
_builder = builder;
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
//添加以ASPNETCORE_開頭的環境變量(ps:判斷當前環境是那個環境)
if (!options.SuppressEnvironmentConfiguration)
{
configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
}
//這里主要加載環境變量
_config = configBuilder.Build();
_builder.ConfigureHostConfiguration(config =>
{
//將上面的配置加載進來
config.AddConfiguration(_config);
//通過配置和特性加載額外的Config(或者不加載配置),通過繼承IHostingStartup無侵入性加載。
ExecuteHostingStartups();
});
//將上面Startup中Config的配置放到Build階段加載
_builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
if (_hostingStartupWebHostBuilder != null)
{
var webhostContext = GetWebHostBuilderContext(context);
_hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
}
});
//增加注入的服務
_builder.ConfigureServices((context, services) =>
{
var webhostContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
//注入一些其他服務
services.AddSingleton(webhostContext.HostingEnvironment);
services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.WebHostOptions = webHostOptions;
options.HostingStartupExceptions = _hostingStartupErrors;
});
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
//可以通過配置的方式查找程序集加載StartUp,但是默認只會加載最后一個StartUp
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
UseStartup(startupType, context, services);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
capture.Throw();
};
});
}
}
});
}
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
//提供.netCore 靜態Web資產(ps:說實話這里不知道有什么用)
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
//使用 Kestrel 配置反向代理
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
})
.ConfigureServices((hostingContext, services) =>
{
//配置啟動的Url
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
//用來獲取客戶端的IP地址
if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
}
//添加路由配置
services.AddRouting();
})
//默認使用IIS
.UseIIS()
//使用進程內的托管模式
.UseIISIntegration();
}
這部分內容可能會多點,源碼總結:
- 添加Memory緩存,添加ASPNETCORE_開頭的環境變量。
- 根據用戶的配置,來加載額外的StartUp中Config配置,但是它的參數是IWebHostBuilder,這部分可以參考微軟文檔StartUp的部分。
- 如果有存在這些配置的話,則統一放到Build階段加載。
- 加載web主機需要的注入的服務,以及判斷是否需要通過程序集來加載StartUp,並且添加一個程序啟動時調用的服務(這里主要是構建HttpContext執行管道)。
- 引用Kestrel,繼承路由和IIS,並且默認使用進程內托管。
- 加載用戶自定義的其他配置,例如默認的調用UseStartup方法。
根據指定配置開始初始化主機Build
public class HostBuilder : IHostBuilder
{
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
private bool _hostBuilt;
private IConfiguration _hostConfiguration;
private IConfiguration _appConfiguration;
private HostBuilderContext _hostBuilderContext;
private HostingEnvironment _hostingEnvironment;
private IServiceProvider _appServices;
private PhysicalFileProvider _defaultProvider;
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
_configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
{
_configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
?? throw new ArgumentNullException(nameof(configureDelegate))));
return this;
}
public IHost Build()
{
//只能執行一次這個方法
if (_hostBuilt)
{
throw new InvalidOperationException(SR.BuildCalled);
}
_hostBuilt = true;
using var diagnosticListener = new DiagnosticListener("Microsoft.Extensions.Hosting");
const string hostBuildingEventName = "HostBuilding";
const string hostBuiltEventName = "HostBuilt";
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName))
{
Write(diagnosticListener, hostBuildingEventName, this);
}
//執行Host配置(應用程序執行路徑,加載_dotnet環境變量,獲取命令行參數,加載預配置)
BuildHostConfiguration();
//設置主機環境變量
CreateHostingEnvironment();
//構建HostBuilderContext實例
CreateHostBuilderContext();
//構建程序配置(加載appsetting.json,環境變量,命令行參數等)
BuildAppConfiguration();
//構造容器,注入服務
CreateServiceProvider();
var host = _appServices.GetRequiredService<IHost>();
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName))
{
Write(diagnosticListener, hostBuiltEventName, host);
}
return host;
}
private static void Write<T>(
DiagnosticSource diagnosticSource,
string name,
T value)
{
diagnosticSource.Write(name, value);
}
private void BuildHostConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions)
{
buildAction(configBuilder);
}
//本質是執行ConfigureProvider中的Load方法,加載對應配置
_hostConfiguration = configBuilder.Build();
}
private void CreateHostingEnvironment()
{
//設置環境變量
_hostingEnvironment = new HostingEnvironment()
{
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
};
if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
{
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
}
//程序運行路徑
_hostingEnvironment.ContentRootFileProvider = _defaultProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
}
private void CreateHostBuilderContext()
{
_hostBuilderContext = new HostBuilderContext(Properties)
{
HostingEnvironment = _hostingEnvironment,
Configuration = _hostConfiguration
};
}
private void BuildAppConfiguration()
{
//對於已經加載過的配置不再重新加載
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
//注意這里是AppConfig
foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
//將新的配置賦值給config
_hostBuilderContext.Configuration = _appConfiguration;
}
private void CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_ => _appConfiguration);
services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost>(_ =>
{
return new Internal.Host(_appServices,
_hostingEnvironment,
_defaultProvider,
_appServices.GetRequiredService<IHostApplicationLifetime>(),
_appServices.GetRequiredService<ILogger<Internal.Host>>(),
_appServices.GetRequiredService<IHostLifetime>(),
_appServices.GetRequiredService<IOptions<HostOptions>>());
});
services.AddOptions().Configure<HostOptions>(options => { options.Initialize(_hostConfiguration); });
services.AddLogging();
//主要加載額外注入的服務
foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
//這里返回object,主要是為了保留擴展,讓用戶自定義的依賴注入框架能夠運行。
object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
{
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
if (_appServices == null)
{
throw new InvalidOperationException(SR.NullIServiceProvider);
}
//可能是想先把IConfiguration加載到內存中
_ = _appServices.GetService<IConfiguration>();
}
}
在上面的兩個小單元可以看出,所有的構造是以委托的方式,最后都加載到HostBuilder內部的委托集合中,源碼總結:
- 加載環境變量和命令行參數。
- 構建HostingEnvironment對象。
- 構建HostBuilderContext對象,里面包含配置和執行環境。
- 加載appsettings.json,環境變量和命令行參數等。
- 注入一些必須的服務,加載日志配置,WebHost里面注入的服務,加載StartUp里面ConfigService里面的服務,以及其他的一些注入的服務,構建容器(最后那里獲取IConfiguration猜測可能是想先緩存到根容器吧)。
運行主機Run
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
//應用程序啟動和關閉事件
await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
combinedCancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
//主要是執行一些后台任務,可以重寫啟動和關閉時要做的操作
foreach (IHostedService hostedService in _hostedServices)
{
//立即執行的任務,例如構建管道就是在這里
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
//執行一些后台任務
if (hostedService is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}
//通知應用程序啟動成功
_applicationLifetime.NotifyStarted();
//程序啟動
_logger.Started();
}
源碼總結:
- 監聽程序的啟動關閉事件。
- 開始執行Hosted服務或者加載后台執行的任務。
- 通過TaskCompletionSource來持續監聽Token,hold住進程。
總結
通過模板來構建的.Net泛型主機,其實已經可以滿足大部分的要求,並且微軟保留大量擴展讓用戶來自定義,當然你也可以構建其他不同的主機類型(如:Web主機或者控制台程序啟動項配置),想了解的可以點擊這里。
以上就是筆者通過閱讀源碼來分析的程序執行流程,因為篇幅問題沒有把所有代碼都放出來,實在是太多了,所以只放了部分代碼,主要是想給閱讀源碼的同學在閱讀的時候找到思路,可能會有一點錯誤,還請評論指正😁。
