1.要了解启动过程,先介绍 IHostingStartup和IStartup 接口
/// <summary>
/// Represents platform specific configuration that will be applied to a <see cref="IWebHostBuilder"/> when building an <see cref="IWebHost"/>.
/// </summary>
public interface IHostingStartup
{
/// <summary>
/// Configure the <see cref="IWebHostBuilder"/>.
/// </summary>
/// <remarks>
/// Configure is intended to be called before user code, allowing a user to overwrite any changes made.
/// </remarks>
/// <param name="builder"></param>
void Configure(IWebHostBuilder builder);
}
/// <summary>
/// Provides an interface for initializing services and middleware used by an application.
/// </summary>
public interface IStartup
{
/// <summary>
/// Register services into the <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
IServiceProvider ConfigureServices(IServiceCollection services);
/// <summary>
/// Configures the application.
/// </summary>
/// <param name="app">An <see cref="IApplicationBuilder"/> for the app to configure.</param>
void Configure(IApplicationBuilder app);
}
从上面2个接口可以看出,IHostingStartup 比 IStartup 不同点是缺少 IServiceProvider ConfigureServices(IServiceCollection services);,其实二者的运行顺序也是不同。
IHostingStartup 用以配置 IWebHostBuilder,.net core 允许有多个 IHostingStartup来实现在不同得地方配置 WebHostBuilder,下面展示一下 IHostingStartup得调用过程。
项目启动首先会执行Program.cs文件中的 Main方法,Program文件只是默认项,可以通过更改项目配置 来实现调用指定类的Main方法,
public class Program
{
// Entry point for the application.
public static Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
.UseKestrel()
// Each of these three sets ApplicationName to the current assembly, which is needed in order to
// scan the assembly for HostingStartupAttributes.
// .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups")
// .Configure(_ => { })
.UseStartup<Startup>();
})
.Build();
return host.RunAsync();
}
}
例1:例子来源.net core 8.0 源码 中 Hosting 的SampleStartups
项目配置
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<StartupObject>SampleStartups.StartupBlockingOnStart</StartupObject>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Hosting" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<Reference Include="Microsoft.Extensions.Configuration.Json" />
</ItemGroup>
</Project>
通过配置 程序启动文件为 SampleStartups.StartupBlockingOnStart
namespace SampleStartups;
public class StartupInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.UseStartup<InjectedStartup>();
}
// Entry point for the application.
public static Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
.UseKestrel()
// Each of these three sets ApplicationName to the current assembly, which is needed in order to
// scan the assembly for HostingStartupAttributes.
// .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups")
// .Configure(_ => { })
.UseStartup<NormalStartup>();
})
.Build();
return host.RunAsync();
}
}
HostBuilder.ConfigureXXX
这里优先介绍HostBuilder一下ConfigureHostConfiguration、ConfigureAppConfiguration、ConfigureServices等方法到底做了什么。其实就是将Action暂存到了一个私有属性变量中,
并没有立即执行。先存在一个list中,后边按一定顺序执行。稍后会介绍在哪里执行这些Action。
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>>();
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;
}
}
我们看一下 new HostBuilder().ConfigureWebHost(Action configure) 中 ConfigureWebHost() 方法中具体执行了什么 ,
private static IHostBuilder ConfigureWebHost(
this IHostBuilder builder,
Func<IHostBuilder, WebHostBuilderOptions, IWebHostBuilder> createWebHostBuilder,
Action<IWebHostBuilder> configure,
Action<WebHostBuilderOptions> configureWebHostBuilder)
{
ArgumentNullException.ThrowIfNull(configure);
ArgumentNullException.ThrowIfNull(configureWebHostBuilder);
//先不介绍这里
// Light up custom implementations namely ConfigureHostBuilder which throws.
if (builder is ISupportsConfigureWebHost supportsConfigureWebHost)
{
return supportsConfigureWebHost.ConfigureWebHost(configure, configureWebHostBuilder);
}
//重点看下这里
var webHostBuilderOptions = new WebHostBuilderOptions();
configureWebHostBuilder(webHostBuilderOptions);
//这里创建了webhostBuilder对象,并立即执行传入的action
var webhostBuilder = createWebHostBuilder(builder, webHostBuilderOptions);
//执行传入的action
configure(webhostBuilder);
//此处暂存了一个action ,其实在 createWebHostBuilder(builder, webHostBuilderOptions) 中已经执行了逻辑,暂存多个关键的action
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
return builder;
}
我们看一下在 new GenericWebHostBuilder(hostBuilder, options) 中执行的逻辑
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup
{
//用于暂存关于IStartup的相关类型,
private object? _startupObject;
private readonly object _startupKey = new object();
private AggregateException? _hostingStartupErrors;
private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder;
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
: base(builder, options)
{
//暂存一个HostConfigration的action
_builder.ConfigureHostConfiguration(config =>
{
config.AddConfiguration(_config);
//重点关注下这里,稍后介绍这里会涉及到IHostStartup.Configure(IWebHostBuilder builder 的执行),这里的参数builder 就是 this
// We do this super early but still late enough that we can process the configuration
// wired up by calls to UseSetting
ExecuteHostingStartups();
});
//暂存
// IHostingStartup needs to be executed before any direct methods on the builder
// so register these callbacks first
_builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
if (_hostingStartupWebHostBuilder != null)
{
var webhostContext = GetWebHostBuilderContext(context);
_hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
}
});
//暂存CongfigureServices action
_builder.ConfigureServices((context, services) =>
{
var webhostContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
// Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting
services.AddSingleton(webhostContext.HostingEnvironment);
#pragma warning disable CS0618 // Type or member is obsolete
services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
#pragma warning restore CS0618 // Type or member is obsolete
services.Configure<GenericWebHostServiceOptions>(options =>
{
// Set the options
options.WebHostOptions = webHostOptions;
// Store and forward any startup errors
options.HostingStartupExceptions = _hostingStartupErrors;
});
// REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up
// We need to flow this differently
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
services.TryAddSingleton(DistributedContextPropagator.Current);
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddMetrics();
services.TryAddSingleton<HostingMetrics>();
// IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup)
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
//如果特别指定了startupAssembly ,程序就会扫描程序集获取,这个行为也被暂存在当前的这个action
// Support UseStartup(assemblyName)
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
ScanAssemblyAndRegisterStartup(context, services, webhostContext, webHostOptions);
}
});
}
//删除部分代码
}
在webHostBuilder创建过程中暂存了ConfigureHostConfiguration,ConfigureAppConfiguration,ConfigureServices,那么他们都在什么时候执行,并且是一个什么执行顺序。这就要看一下他们在HostBuilder 中Build过程中的执行顺序了,不废话,直接上代码
public partial class HostBuilder : IHostBuilder
{
/// <summary>
/// Run the given actions to initialize the host. This can only be called once.
/// </summary>
/// <returns>An initialized <see cref="IHost"/></returns>
/// <remarks>Adds basic services to the host such as application lifetime, host environment, and logging.</remarks>
public IHost Build()
{
if (_hostBuilt)
{
throw new InvalidOperationException(SR.BuildCalled);
}
_hostBuilt = true;
// REVIEW: If we want to raise more events outside of these calls then we will need to
// stash this in a field.
using DiagnosticListener diagnosticListener = LogHostBuilding(this);
// 1.初始化主机(Host)配置
InitializeHostConfiguration();
// 2.初始化 HostingEnvironment
InitializeHostingEnvironment();
// 3.初始化 HostBuilderContext
InitializeHostBuilderContext();
// 4.初始化应用(App)配置
InitializeAppConfiguration();
// 5.初始化服务并创建 Service Provider
InitializeServiceProvider();
return ResolveHost(_appServices, diagnosticListener);
}
}
不难看出 在 BuildHostConfiguration()方法中,会执行在上面介绍 new WebHostBuilder() 构造函数中暂存的一个action ,看下方代码暂存的那个action
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup
{
//省略一下代码。。。。
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
: base(builder, options)
{
//暂存一个HostConfigration的action,这个action 最终会在Build()方法中的第一阶段初始 化主机Host配置InitializeHostConfiguration()中执行。
_builder.ConfigureHostConfiguration(config =>
{
config.AddConfiguration(_config);
//重点关注下这里,稍后介绍
// We do this super early but still late enough that we can process the configuration
// wired up by calls to UseSetting
ExecuteHostingStartups();
});
}
}
此时为执行 ExecuteHostingStartups,此处涉及执行 HostStartup相关逻辑。
internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup
{
private object? _startupObject;
private readonly object _startupKey = new object();
private AggregateException? _hostingStartupErrors;
private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder;
private void ExecuteHostingStartups()
{
var webHostOptions = new WebHostOptions(_config);
if (webHostOptions.PreventHostingStartup)
{
return;
}
var exceptions = new List<Exception>();
var processed = new HashSet<Assembly>();
_hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);
// Execute the hosting startup assemblies
foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies())
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
if (!processed.Add(assembly))
{
// Already processed, skip it
continue;
}
//找出有标记HostingStartupAttribute的信息
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
{
//根据标签中指定的类型,类型需要继承IHostSrartup接口 ,实例化该类型的对象,然后执行对象Configue方法 来配置WebHostBuilder
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType)!;
hostingStartup.Configure(_hostingStartupWebHostBuilder);
}
}
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);
}
}
}
总结:
执行IHostStartup.Configure(IWebHostBuilder builder) 方法的一个委托被暂存在_configureHostCofigActions中,
最终会在HostBuilder.Build()的第一阶段InitializeHostConfiguration()方法中执行,执行顺序早于 InitializeHostingEnvironment()、InitializeHostBuilderContext()、
InitializeAppConfiguration()、InitializeServiceProvider();