ASP.NET Core : 七.一张图看透启动背后的秘密

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

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

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

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

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

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

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

一、整体流程图
先上图, 觉得看不清可以点击看大图或者下载后放大查看。

image.png

图一 (点击放大)

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

1 public class WebHostBuilder : IWebHostBuilder
2 {
3 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
4
5 private IConfiguration _config;
6 public IWebHostBuilder UseSetting(string key, string value)
7 {
8 _config[key] = value;
9 return this;
10 }
22 public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
23 {
24 if (configureServices == null)
25 {
26 throw new ArgumentNullException(nameof(configureServices));
27 }
29 _configureServicesDelegates.Add(configureServices);
30 return this;
31 }
32 }

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

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

三、UseStartup()
  Create­DefaultBuilder之后调用UseStartup(),指定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,

1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
2 {
3 //...省略...
4 var services = new ServiceCollection();
5 services.AddSingleton(_hostingEnvironment);
6 services.AddSingleton(_context);
7 //....各种Add....
9 foreach (var configureServices in _configureServicesDelegates)
10 {
11 configureServices(_context, services);
12 }
14 return services;
15 }

  在这个方法里创建了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();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);

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

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

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

var builderFactory = _applicationServices.GetRequiredService();
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仍是原来的版本,即使删除下载的文件后再次重新获取也一样, 应该是和新建项目默认引用的依赖版本有关。

目录
相关文章
|
6天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
24 5
|
2月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
24天前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
35 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
14天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
23 3
|
2月前
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
mcr.microsoft.com/dotnet/core/aspnet:2.1安装libgdiplus
32 1
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
开发框架 前端开发 .NET
ASP.NET Core 核心特性学习笔记「下」
ASP.NET Core 核心特性学习笔记「下」
|
开发框架 前端开发 中间件
ASP.NET Core 核心特性学习笔记「上」
ASP.NET Core 核心特性学习笔记「上」
|
SQL 机器学习/深度学习 Cloud Native
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记
- 状态 - 自动变更检测 - 不查询删除和更新 - 并发
253 0
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记
|
SQL Cloud Native 架构师
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 查询)--学习笔记
- 关联数据加载 - 客户端与服务端运算 - 跟踪与不跟踪 - 复杂查询运算 - 原生 SQL 查询 - 全局查询筛选器
246 0
.NET 云原生架构师训练营(模块二 基础巩固 EF Core 查询)--学习笔记