本系列将分析ASP.NET Core运行原理

本节将分析WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build();代码。

源代码参考.NET Core 2.0.0

问题概要

  1. Hosting中有哪2个ServiceProvider,各自如何创建,以及有哪些ServiceCollection。
  2. 什么时候执行Startup的ConfigureServices 和 Configure方法
  3. 什么时候注册和使用的Server
  4. 手工模拟一个DefaultWebHost环境

WebHost.CreateDefaultBuilder(args)

该方法为WebHost类的静态方法,内部创建1个WebHostBuilder。

  1. 参数args将作为配置项
  2. 添加了Kestrel、Configuration、Logging、IISIntegration中间件,同时配置ContentRoot和DefaultServiceProvider
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
} config.AddEnvironmentVariables(); if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseIISIntegration()
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
});
return builder;
}

UseKestrel

UseKestrel()方法中,注册了3个服务到List<Action<WebHostBuilderContext, IServiceCollection>>字段上。(以供后续注册)

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((Action<IServiceCollection>) (services =>
{
services.AddSingleton<ITransportFactory, LibuvTransportFactory>();
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
services.AddSingleton<IServer, KestrelServer>();
}));
} public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
this._configureServicesDelegates.Add((_, services) => configureServices(services));
return (IWebHostBuilder) this;
}

UseContentRoot

UseContentRoot方法则是添加到IConfiguration字段上,这个字段在构造函数初始化

this._config = (IConfiguration) new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();

public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot)
{
if (contentRoot == null)
throw new ArgumentNullException(nameof (contentRoot));
return hostBuilder.UseSetting(WebHostDefaults.ContentRootKey, contentRoot);
} public IWebHostBuilder UseSetting(string key, string value)
{
this._config[key] = value;
return (IWebHostBuilder) this;
}

ConfigureAppConfiguration

ConfigureAppConfiguration方法是添加到List<Action<WebHostBuilderContext, IConfigurationBuilder>>字段上

在外部添加了

AddJsonFile("appsettings.json")AddJsonFile(string.Format("appsettings.{0}.json", (object) hostingEnvironment.EnvironmentName))

AddEnvironmentVariables()

AddCommandLine(args)

public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
this._configureAppConfigurationBuilderDelegates.Add(configureDelegate);
return (IWebHostBuilder) this;
}

ConfigureLogging

ConfigureLogging注册Log到ServiceCollection上

在外部添加了3个ILoggerProvider

logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));

logging.AddConsole();

logging.AddDebug();

public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices((context, services) => services.AddLogging(builder => configureLogging(context, builder));
} public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
this._configureServicesDelegates.Add(configureServices);
return (IWebHostBuilder) this;
}

UseDefaultServiceProvider

UseDefaultServiceProvider配置和替换服务

var options = new ServiceProviderOptions { ValidateScopes = context.HostingEnvironment.IsDevelopment()};
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>((IServiceProviderFactory<IServiceCollection>) new DefaultServiceProviderFactory(options)));

UseStartup

UseStartup相当于注册了一个IStartup服务。

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
string name = startupType.GetTypeInfo().Assembly.GetName().Name;
return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action<IServiceCollection>) (services =>
{
if (typeof (IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), startupType);
else
ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), (Func<IServiceProvider, object>) (sp =>
{
IHostingEnvironment requiredService = sp.GetRequiredService<IHostingEnvironment>();
return (object) new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.EnvironmentName));
}));
}));
}

根据Startup是否继承IStartup,来决定注册的方式。未继承的时候,会使用ConventionBasedStartup来封装自定义的Startup。

Build

Build方法是WebHostBuilder最终的目的,将构造一个WebHost返回。

同时初始化WebHost对象,WebHostBuilder.Build代码:

public IWebHost Build()
{
var hostingServices = BuildCommonServices(out var hostingStartupErrors);
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider(); AddApplicationServices(applicationServices, hostingServiceProvider); var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors); host.Initialize(); return host;
}

在Build方法中,BuildCommonServices最为重要,将构造第一个ServiceCollection。这里我们称为hostingServices

将包含hostEnv、Config、ApplicationBuilder、Logging、StartupFilter、Startup、Server。参考BuildCommonServices

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
if (!this._options.PreventHostingStartup)
{
foreach (string hostingStartupAssembly in (IEnumerable<string>) this._options.HostingStartupAssemblies)
{
foreach (HostingStartupAttribute customAttribute in Assembly.Load(new AssemblyName(hostingStartupAssembly)).GetCustomAttributes<HostingStartupAttribute>())
((IHostingStartup) Activator.CreateInstance(customAttribute.HostingStartupType)).Configure((IWebHostBuilder) this);
}
}
ServiceCollection services = new ServiceCollection();
// hostEnv
_hostingEnvironment.Initialize(this._options.ApplicationName, this.ResolveContentRootPath(this._options.ContentRootPath, AppContext.BaseDirectory), this._options);
services.AddSingleton<IHostingEnvironment>(this._hostingEnvironment);
// config
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder().SetBasePath(this._hostingEnvironment.ContentRootPath).AddInMemoryCollection(this._config.AsEnumerable());
foreach (Action<WebHostBuilderContext, IConfigurationBuilder> configurationBuilderDelegate in this._configureAppConfigurationBuilderDelegates)
configurationBuilderDelegate(this._context, configurationBuilder);
IConfigurationRoot configurationRoot = configurationBuilder.Build();
services.AddSingleton<IConfiguration>((IConfiguration) configurationRoot);
services.AddOptions();
// application
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
// log
services.AddLogging();
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
// 配置的StartupType
if (!string.IsNullOrEmpty(this._options.StartupAssembly))
{
Type startupType = StartupLoader.FindStartupType(this._options.StartupAssembly, this._hostingEnvironment.EnvironmentName);
if (typeof (IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), startupType);
else
ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), new ConventionBasedStartup(StartupLoader.LoadMethods(startupType)));
}
// UseStartup、UseKestrel、ConfigureLogging
foreach (Action<WebHostBuilderContext, IServiceCollection> servicesDelegate in this._configureServicesDelegates)
servicesDelegate(this._context, (IServiceCollection) services);
return (IServiceCollection) services;
}

hostingServices 创建完成后,会马上拷贝一份applicationServices提供给WebHost使用,同时创建第一个ServiceProvider hostingServiceProvider

host.Initialize()该方法则是初始化WebHost

  1. 生成第二个ServiceProvider _applicationServices(微软的内部字段命名,感觉不太规范);
  2. 初始化Server,读取绑定的地址。
  3. 创建Application管道,生成RequestDelegate
public void Initialize()
{
_startup = _hostingServiceProvider.GetService<IStartup>();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
EnsureServer();
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices; var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
}
configure(builder); this._application = builder.Build(); // RequestDelegate
}

WebHost.Run

创建完 WebHost 之后,便调用它的 Run 方法,而 Run 方法会去调用 WebHost 的 StartAsync 方法

  1. 调用Server.Start,将Initialize方法创建的Application管道传入以供处理消息
  2. 执行HostedServiceExecutor.StartAsync方法
public static void Run(this IWebHost host)
{
host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.").GetAwaiter().GetResult();
} private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage)
{
await host.StartAsync(token);
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; if (serverAddresses != null)
foreach (var address in serverAddresses)
Console.WriteLine($"Now listening on: {address}"); if (!string.IsNullOrEmpty(shutdownMessage))
Console.WriteLine(shutdownMessage);
} public virtual async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken))
{
Initialize();
_applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); _applicationLifetime?.NotifyStarted();
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
}

问题解答

  1. hostingServices(WebHostBuilder) 和 _applicationServices(WebHost)。区别是_applicationServices中比hostingServices多处Startup方法注册的服务。
  2. 都是WebHost中执行的。ConfigureServices在_applicationServices生成前,Configure在_applicationServices生成后。
  3. 最初的注册在WebHostBuilder的UseKestrel,使用在WebHost的Run方法

手工模拟一个DefaultWebHost环境

new WebHostBuilder()
.UseKestrel() // 使用Kestrel服务器
.UseContentRoot(Directory.GetCurrentDirectory()) // 配置根目录 会在ConfigurationBuilder、 IHostingEnvironment(后续其他中间件) 和 WebHostOptions(WebHost)用到。
.ConfigureAppConfiguration((context, builder) => builder.AddJsonFile("appsetting.json", true, true)) // 添加配置源
.ConfigureLogging(builder => builder.AddConsole()) // 添加日志适配器
.UseStartup<Startup>() // 选择Startup
.Build().Run(); // 生成WebHost 并 启动

本文链接:http://neverc.cnblogs.com/p/7988226.html

【ASP.NET Core】运行原理(1):创建WebHost的更多相关文章

  1. ASP.NET Core 运行原理解剖[1]:Hosting

    ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...

  2. ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

    在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索.而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做 ...

  3. ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

    HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...

  4. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  5. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  6. ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

    在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...

  7. ASP.NET Core 运行原理解剖[5]:Authentication

    在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...

  8. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  9. ASP.NET Core 运行原理剖析 (转载)

    1.1. 概述 在ASP.NET Core之前,ASP.NET Framework应用程序由IIS加载.Web应用程序的入口点由InetMgr.exe创建并调用托管.以初始化过程中触发HttpAppl ...

随机推荐

  1. PHP中抽象方法、抽象类和接口的用法

    在类中,没有方法体的方法就是抽象方法. abstract 可见性 function 方法名称(参数1,.....);      // 如果没有显示地指定可见性,则默认为public 如: public ...

  2. November 21st 2016 Week 48th Monday

    A bird is known by its note, and a man by his talk. 闻其声而知鸟,听其言而知人. Listen to what a man talks, watch ...

  3. 2018.08.30 21:12 第一个Django程序完成

    from django.http import HttpResponse def hello(request): return HttpResponse("Hello world ! &qu ...

  4. APP的案例分析

    很多同学有误解,软件项目管理是否就是理论课?或者是几个牛人拼命写代码,其他人打酱油的课?要不然就是学习一个程序语言,搞一个职业培训的课?都不对,软件项目管理有理论,有实践,更重要的是分析,思辨,总结. ...

  5. PAT乙级1017

    1017 A除以B (20 分)   本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数.你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立. 输入格式: 输 ...

  6. module object has no attribute dumps的解决方法

    问题产生原因: python的版本过低,其中的json包年久失修,需要更新 解决方法: 删除就的json包 >>> import json >>> print js ...

  7. NYOJ-171 聪明的kk 填表法 普通dp

    题目链接: http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=171 聪明的kk 时间限制:1000 ms  |  内存限制:65535 KB 难 ...

  8. Xcode解决“Implicit declaration of function 'XXX' is invalid in C99” 警告或报错

    1.Build Setting>>>C Language Dialect,然后选择GNU99[-std=gnu99] (选择看项目实际要求). 2.Build Setting> ...

  9. iOS渐变导航栏封装

    由于最近开发的几个项目都有渐变导航栏,每次写的时候都要copy一堆关于导航渐变相关的代码,显得类很冗余,所以花了点时间封装了一个渐变类,直接继承就可以满足大部分需求啦,这里简单写一下心路历程: 渐变的 ...

  10. jQuery带缩略图轮播效果图片切换带缩略图

    以上为效果图 HTML代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /& ...