为什么我们可以在Startup这个 “孤零零的” 类中配置依赖注入和管道?

  它是什么时候被实例化并且调用的?

  参数中的IServiceCollection services是怎么来的?

  处理管道是怎么构建起来的?

  启动过程中,系统“默默的”做了哪些准备工作?

  上一篇文章讲了ASP.NET Core中的依赖注入(系列目录), 而它的配置是在Startup这个文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup这个类也没有继承任何类或者接口。 深入的想一想,可能会冒出类似上面列出的好多问题,下面用一幅图来看透它。

一、整体流程图

先上图, 觉得看不清可以点击看大图或者下载后放大查看。

图一  (点击放大

二、WebHostBuilder

  应用程序在Main方法之后通过调用Create­DefaultBuilder方法创建并配置WebHostBuilder,

     public class WebHostBuilder : IWebHostBuilder
{
private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; private IConfiguration _config;
public IWebHostBuilder UseSetting(string key, string value)
{
_config[key] = value;
return this;
}
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
if (configureServices == null)
{
throw new ArgumentNullException(nameof(configureServices));
}
_configureServicesDelegates.Add(configureServices);
return this;
}
}

WebHostBuilder存在一个重要的集合① private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; , 通过 ConfigureServices 方法将需要的Action加入进来。

UseSetting是一个用于设置Key-Value的方法, 一些常用的配置均会通过此方法写入_config中。

三、UseStartup<Startup>()

  Create­DefaultBuilder之后调用UseStartup<Startup>(),指定Startup为启动类。

        public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.ConfigureServices(services =>
{
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
services.AddSingleton(typeof(IStartup), startupType);
}
else
{
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});
}
});
}

首先获取Startup类对应的AssemblyName, 调用UseSetting方法将其设置为WebHostDefaults.ApplicationKey(“applicationName”)的值。

然后调用WebHostBuilder的②ConfigureServices方法,将一个Action写入WebHostBuilder 的 configureServicesDelegates中。

这个Action的意思就是说,如果这个被指定的类startupType是一个实现了IStartup的类, 那么将其通过AddSingleton注册到services 这个ServiceCollection中, 如果不是, 那么将其“转换”成 ConventionBasedStartup 这个实现了 IStartup的类后再进行注册。这里涉及到一个StartupLoader的LoadMethods()方法,会通过字符串的方式查找“ConfigureServices”、“Configure{ environmentName}Services”这样的方法。

注意:这里只是将一个Action写入了configureServicesDelegates, 而不是已经执行了对IStartup的注册, 因为这个Action尚未执行,services也还不存在。就像菩萨对八戒说: 八戒(Startup)你先在高老庄等着吧, 将来有个和尚带领一个取经小分队(ServiceCollection services )过来的时候你加入他们。

其实在Create­DefaultBuilder方法中的几个UseXXX的方法也是这样通过ConfigureServices将对应的Action写入了configureServicesDelegates, 等待唐僧的到来。

四、WebHostBuilder.Build()

创建并配置好的WebHostBuilder开始通过Build方法创建WebHost了, 首先是BuildCommonServices,

 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
//...省略...
var services = new ServiceCollection();
services.AddSingleton(_hostingEnvironment);
services.AddSingleton(_context);
//....各种Add....
foreach (var configureServices in _configureServicesDelegates)
{
configureServices(_context, services);
}
return services;
}

  在这个方法里创建了ServiceCollection services(以唐僧为首的取经小分队), 然后通过各种Add方法注册了好多内容进去(收了悟空),然后③foreach 之前暂存在configureServicesDelegates中的各个Action,传入services逐一执行, 将之前需要注册的内容注册到services中, 这里就包括Startup(八戒),注意这里仅是进行了注册,而未执行Startup的方法。

  处理好的这个services被BuildCommonServices返回后赋值给 hostingServices,然后 hostingServices经过Clone()生成 applicationServices,再由这个 applicationServices进行 GetProviderFromFactory(hostingServices)生成一个 IServiceProvider hostingServiceProvider.经过一系列的处理后,可以创建WebHost了。

var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors); host.Initialize();

将生成的applicationServices 和 hostingServiceProvider作为参数传递给新生成的WebHost。接下来就是这个WebHost的 Initialize()。

五、WebHost.Initialize()

WebHost的 Initialize()的主要工作就是BuildApplication()。

EnsureApplicationServices(): 用来处理WebHost的 private IServiceProvider _applicationServices ,④Startup的ConfigureServices方法在这里被调用。

_startup = _hostingServiceProvider.GetRequiredService<IStartup>();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);

通过 GetRequiredService<IStartup>() 获取到我们的_startup, 然后调用这个_startup的 ⑤ConfigureServices 方法,这就是我们用于依赖注入的startup类的ConfigureServices方法了。

所以,_applicationServices是根据_applicationServiceCollection 加上我们在_startup中注册的内容之后重新生成的 IServiceProvider。

EnsureServer()⑥:通过 GetRequiredService<IServer>()获取Server并配置监听地址。

var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices;

获取到 IApplicationBuilderFactory并通过它⑦创建 IApplicationBuilder,并将上面创建的_applicationServices赋值给它的ApplicationServices,它还有个重要的集合_components

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

从_components的类型可以看出它其实是中间件的集合,是该调用我们的_startup的Configure方法的时候了。

先获取定义的IStartupFilter, ⑧foreach这些IStartupFilter并与_startup的Configure方法一起将配置的中间件写入_components,然后通过 Build()创建RequestDelegate _application,

在Build()中对_components进行处理生成请求处理管道,关于IStartupFilter和生成管道这部分将在下篇文章进行详细说明。

六、WebHost.Run()

  WebHost创建完毕, 最后一步就是Run起来了,WebHost的Run()会调用它的方法StartAsync()

public virtual async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
//......var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
_hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
//.....
}

  在之前的文章中我们知道,请求是经过 Server监听=>处理成httpContext=>Application处理,所以这里首先传入上面创建的_application和一个httpContextFactory来⑨生成一个HostingApplication,并将这个HostingApplication传入Server的StartAsync(), 当Server监听到请求之后, 后面的工作由HostingApplication来完成。

  ⑩hostedServiceExecutor.StartAsync()方法用来开启一个后台运行的服务,一些需要后台运行的操作比如定期刷新缓存等可以放到这里来。

七、更新

  感谢dudu的留言,去github上看了一下WebHost的最新源码,BuildApplication()不再包含EnsureApplicationServices()的调用,并且转移到了WebHost.StartAsync() 中进行; WebHost.Initialize() 中由原本调用BuildApplication()改为调用原本放在BuildApplication()中调用的EnsureApplicationServices()。

  通过VS加载符号的方式调试获取到的WebHost仍是原来的版本,即使删除下载的文件后再次重新获取也一样, 应该是和新建项目默认引用的依赖版本有关。

ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密的更多相关文章

  1. [转]探索ASP.NET Core 3.0 系列

    这是该系列的第一篇文章:探索ASP.NET Core 3.0. 第1部分-探索新的项目文件Program.cs和通用主机(本文) 第2部分-比较ASP.NET Core 3.0模板之间的Startup ...

  2. [亲测]七步学会ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问

    前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...

  3. ASP.NET Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middleware)的方式注册在管道中.显而易见这样的设计非常松耦合 ...

  4. 丙申年把真假美猴王囚禁在容器中跑 ASP.NET Core 1.0

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  5. .Net Core 2.0生态(3):ASP.NET Core 2.0 特性介绍和使用指南

    ASP.NET Core 2.0 发布日期:2017年8月14日 ASP.NET团队宣布ASP.NET Core 2.0正式发布,发布Visual Studio 2017 15.3支持ASP.NET ...

  6. ASP.net core 2.0.0 中 asp.net identity 2.0.0 的基本使用(一)—修改数据库连接

    开发环境:vs2017  版本:15.3.5 项目环境:.net framework 4.6.1    模板asp.net core 2.0  Web应用程序(模型视图控制器) 身份验证:个人用户账号 ...

  7. ASP.NET Core 2.0 : 六. 举个例子来聊聊它的依赖注入

    本文通过一个维修工与工具库的例子形象的描述一下为什么要用依赖注入.它的工作原理是什么样的, 然后根据这个类比一下ASP.NET Core 中的依赖注入, 从而深刻了解它的使用方法.注意事项以及回收机制 ...

  8. ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64)

    ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64) 七牛图片上传 SDK(.NET 版本):https://developer.qiniu.com/kodo/sdk/ ...

  9. ASP.NET Core 3.0 使用gRPC

    一.简介 gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架. gRPC使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建.它使用HTTP/2作为通信协 ...

随机推荐

  1. hdu1698 Just a Hook 线段树

    共有Q个更新,每次更新给更新的区间一个标记,表示该区间是在哪一次被更新,最后统计答案是以最近被更新的值为答案. AC代码: #include<cstdio> const int maxn= ...

  2. java网络编程(6)——实现一个服务器把小写转大写

    实现一个服务器,通过我们发送的文本数据,然后转回大写放回,实现一个服务端与客户端的交互,用over来作为结束标记,具体代码如下: 客户端: package com.seven.tcp; import ...

  3. 情景linux--如何摆脱深路径的频繁切换烦恼?

    情景 通常情况下,在linux系统上切换目录的成本很低,使用cd命令就可以了.如果需要在一个目录的不同的子目录和其父目录之间切换,进入到这个目录之后,再使用相对路径会比较方便.如果要切换的目录的路径较 ...

  4. SpringBoot实战 之 接口日志篇

    在本篇文章中不会详细介绍日志如何配置.如果切换另外一种日志工具之类的内容,只用于记录作者本人在工作过程中对日志的几种处理方式. 1. Debug 日志管理 在开发的过程中,总会遇到各种莫名其妙的问题, ...

  5. JavaScript设计模式之策略模式

    所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...

  6. linux web服务器及LAMP动态网站平台搭建

    (vim补:)vim另存为:x,x w 目标文件路径vim批量删除x:x,x d Web服务:基于B/S架构的web通信服务端:支持HTTP协议的网页提供程序客户端:按标记规范显示网页的浏览器程序客户 ...

  7. zTree实现删除树节点

    zTree实现删除树节点 1.实现源码 <!DOCTYPE html> <html> <head> <title>zTree实现基本树</titl ...

  8. linux下驱动模块化编译,动态加载以及卸载

    步骤:: 1.编写first_driver_hello.c文件,将其放在/linux2.6.32/drivers/char路径下: 2.在/linux2.6.32/drivers/cha/Kconfi ...

  9. 芝麻HTTP:在无GUI的CentOS上使用Selenium+Chrome

    各位小伙伴儿的采集日常是不是被JavaScript的各种点击事件折腾的欲仙欲死啊?好不容易找到个Selenium+Chrome可以解决问题! 但是另一个▄█▀█●的事实摆在面前,服务器都特么没有GUI ...

  10. WPF基础篇之控件模板(ControlTemplate)

    WPF中每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应.我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件. 与Style不同,Style只能改变控件 ...