ASP.NET Core - 源码解析 - Program.cs (二)
ASP.NET Core - 源码解析 - Program.cs (二)
本篇已收录至 asp.net core 随笔系列
代码解析
接着上文继续解析, 当CreateWebHostBuilder
方法执行完毕后, main函数中使用 IWebHostBuilder
的后续操作.
//为什么关注这个类, 因为这里有main函数, 一般来说main函数都是程序启动的时候的启动类. 看一下这行代码:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
让这个 WebHostBuilder 对象 build一个 webhost 并run起来
.Build().Run();
.Build()
Builds an Microsoft.AspNetCore.Hosting.IWebHost which hosts a web application.
看一下 WebHostBuilder 的源码:
/// <summary>
/// Builds the required services and an <see cref="IWebHost"/> which hosts a web application.
/// </summary>
public IWebHost Build()
{
if (_webHostBuilt)
{
throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
}
_webHostBuilt = true;
var hostingServices = BuildCommonServices(out var hostingStartupErrors);
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = GetProviderFromFactory(hostingServices);
if (!_options.SuppressStatusMessages)
{
// Warn about deprecated environment variables
if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
{
Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
{
Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
{
Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
}
}
AddApplicationServices(applicationServices, hostingServiceProvider);
var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors);
try
{
host.Initialize();
var logger = host.Services.GetRequiredService<ILogger<WebHost>>();
// Warn about duplicate HostingStartupAssemblies
foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
{
logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
}
return host;
}
catch
{
// Dispose the host if there's a failure to initialize, this should clean up
// will dispose services that were constructed until the exception was thrown
host.Dispose();
throw;
}
IServiceProvider GetProviderFromFactory(IServiceCollection collection)
{
var provider = collection.BuildServiceProvider();
var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();
if (factory != null && !(factory is DefaultServiceProviderFactory))
{
using (provider)
{
return factory.CreateServiceProvider(factory.CreateBuilder(collection));
}
}
return provider;
}
}
好这么大的方法我们慢慢分析, 首先看这个方法中的 BuildCommonService
方法做了什么?
private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
hostingStartupErrors = null;
_options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
if (!_options.PreventHostingStartup)
{
var exceptions = new List<Exception>();
// Execute the hosting startup assemblies
foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
{
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);
}
}
var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory);
// Initialize the hosting environment
_hostingEnvironment.Initialize(contentRootPath, _options);
_context.HostingEnvironment = _hostingEnvironment;
var services = new ServiceCollection();
services.AddSingleton(_options);
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton(_context);
var builder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_config);
foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
{
configureAppConfiguration(_context, builder);
}
var configuration = builder.Build();
services.AddSingleton<IConfiguration>(configuration);
_context.Configuration = configuration;
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();
services.AddLogging();
// 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;
});
}
}
foreach (var configureServices in _configureServicesDelegates)
{
configureServices(_context, services);
}
return services;
}
这个方法的返回值是 IServiceCollection
, 之前的文章中我们解释过这个对象的概念, 表示当前容器中各服务的配置集合,ASP.NET Core内置的依赖注入容器. 既然是容器, 我们不妨看看容器的构造.
这个命名空间不在 asp.net core 的源码里面, 需要另行下载, 地址是 Microsoft.Extensions.DependencyInjection, 当然你也可以用反编译工具获取源码.
ServiceDescriptor
的官方解释是:
Describes a service with its service type, implementation, and lifetime.
BuildCommonService
方法中对于 ServiceCollection
的实例 services 进行一些注册 service 的操作.
注册的 service 根据需要的作用域和生命周期不同, 对应的注册的方法也不同, 分别是 AddSingleton
, AddScoped
, AddTransient
, 而实现这些注册方法的命名空间是 Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions
, 是 ServiceCollection
的扩展.
Transient objects are always different; a new instance is provided to every controller and every service.
Scoped objects are the same within a request, but different across different requests.
Singleton objects are the same for every object and every request.
IWebHostBuilder
在执行 build 时, 会将很多 service, 包括我们自己的 startup.cs 文件中的 service 注入到 IServiceCollection 容器内. 当注入 service 完毕后, 开始构建 WebHost
对象的过程:
var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors);
try
{
host.Initialize();
var logger = host.Services.GetRequiredService<ILogger<WebHost>>();
// Warn about duplicate HostingStartupAssemblies
foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
{
logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
}
return host;
}
catch
{
// Dispose the host if there's a failure to initialize, this should clean up
// will dispose services that were constructed until the exception was thrown
host.Dispose();
throw;
}
好了 至此一个 WebHost build出来了.
Run()
Runs a web application and block the calling thread until host shutdown.
话不多说,直接上代码:
/// <summary>
/// Runs a web application and block the calling thread until host shutdown.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
public static void Run(this IWebHost host)
{
host.RunAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Runs a web application and returns a Task that only completes when the token is triggered or shutdown is triggered.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
/// <param name="token">The token to trigger shutdown.</param>
public static async Task RunAsync(this IWebHost host, CancellationToken token = default)
{
// Wait for token shutdown if it can be canceled
if (token.CanBeCanceled)
{
await host.RunAsync(token, shutdownMessage: null);
return;
}
// If token cannot be canceled, attach Ctrl+C and SIGTERM shutdown
var done = new ManualResetEventSlim(false);
using (var cts = new CancellationTokenSource())
{
var shutdownMessage = host.Services.GetRequiredService<WebHostOptions>().SuppressStatusMessages ? string.Empty : "Application is shutting down...";
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: shutdownMessage);
try
{
await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
}
finally
{
done.Set();
}
}
}
重点是下面, 我们关注一下启动代码里面, Server
, 以及如何启动的 Server
:
public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
HostingEventSource.Log.HostStart();
_logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
_logger.Starting();
var application = BuildApplication();
_applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
// Fire IApplicationLifetime.Started
_applicationLifetime?.NotifyStarted();
// Fire IHostedService.Start
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
_logger.Started();
// Log the fact that we did load hosting startup assemblies.
if (_logger.IsEnabled(LogLevel.Debug))
{
foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
{
_logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
}
}
if (_hostingStartupErrors != null)
{
foreach (var exception in _hostingStartupErrors.InnerExceptions)
{
_logger.HostingStartupAssemblyError(exception);
}
}
}