.netcore入门24:asp.net core源码分析之Startup调用时机

环境:

  • window 10
  • .netcore 3.1
  • vs2019 16.5.1
  • dnspy v6.1.4

说明:
上一篇介绍到了asp.net core中的通用主机,这个通用主机封装了日志、配置、依赖容器等资源,并且里面包括了一系列的主机服务(IHostedService),而我们常用的web服务(GenericWebHostService:IHostedService)就是其中的一个主机服务。这篇就来分析,asp.net core框架是怎样将web服务注入到通用主机中的,并且Startup是怎么被调用的。

问题:
在讲asp.net core中的web服务之前,先抛出了几个问题:

  • 什么时候执行webBuilder.UseStartup<Startup>();
  • 什么时候创建好依赖容器,什么时候可以使用它,在Startup中可以使用它吗?
  • 什么时候实例化的Startup,构造函数中可以获取哪些对象?
  • 什么时候调用的Startup的ConfigureServices方法,方法参数中可以获取哪些对象?
  • 什么时候调用的Startup的Configure方法,方法参数中可以获取哪些对象?

一、web服务和Startup被调用的流程图

在上一篇,我们用了一个流程图来说明通用主机的构建和启动过程,但仅仅讨论了通用主机,这次我们把web服务加入进去:
在这里插入图片描述

二、关于Startup中的疑问

通过上图中的描述,我们已经可以回答上面那些问题了:

  • 什么时候执行webBuilder.UseStartup<Startup>();

    在进行主机配置的时候,即:在向主机构建者对象中加入构建逻辑的时候。
    通用主机的运转顺序为:创造构建者对象–>向构建者对象中加入构建逻辑–>构建者对象执行Build方法构建出主机–>启动主机。

  • 什么时候创建好依赖容器,什么时候可以使用它,在Startup中可以使用它吗?

    在主机Build完成后会创建好依赖容器,但Startup的实例化和调用ConfigureServices方法之前并没有创建依赖容器,所以不能在Startup的构造参数和ConfigureServices方法参数中使用依赖容器(注:Startup中的参数不是从主机的依赖容器中获取的,而是根据简单的类型判断提供的而且只能提供两类对象,下面会有讲到),但是在Configure方法中可以从依赖容器中获取对象,因为Startup中的Configure方法是在主机Run的时候被调用的,而此时依赖容器已经创建完毕。

  • 什么时候实例化的Startup,构造函数中可以获取哪些对象?

    在主机Build的时候会实例化Startup,因为此时还没有创建依赖容器所以不能从依赖容器中获取对象,但是你仍然可以传入两类参数(这两类参数提供的方式是简单的if/else判断,并没有牵扯到依赖容器,事实上在依赖容器创建之前这两类参数已经创建完成了,只不过还没有放到依赖容器中而已),这两类参数是:
    1.环境: IHostingEnvironment、IWebHostEnvironment和IHostEnvironment其中的一种,无论你写它们中的哪个,获取到的都是同一个对象
    2.配置: IConfiguration

  • 什么时候调用的Startup的ConfigureServices方法,方法参数中可以获取哪些对象?

    在实例化Startup后会马上调用ConfigureServices方法,方法参数最多只能有IServiceCollection。
    注:调用这个方法时,主机的依赖容器并没有创建。

  • 什么时候调用的Startup的Configure方法,方法参数中可以获取哪些对象?

    在主机启动(Host.Run())时首先实例化Web服务(GenericWebHostService),然后再开启web服务,而Configure方法是在实例化web服务的过程中调用的,所以在Configure方法中你可以从依赖容器中获取需要的实例。

三、从源码角度看web服务(GenericWebHostService)和Startup的执行顺序

3.1 web服务是怎么注入到通用主机的?

首先我们来看webapi项目生成代码:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

在上面代码中,我们通过.ConfigureWebHostDefaults(...)将web服务注入进去的,其他的代码都是针对通用主机的,我们不管,调试进入ConfigureWebHostDefaults方法:
在这里插入图片描述
通过上图可以看出,我们创建了一个web主机服务的构造者对象(GenericWebHostBuilder),而这个 GenericWebHostBuilder其实就是封装了通用主机构造者对象(HostBuilder) 而已,那么通过GenericWebHostBuilder注入的构建逻辑也就存储到了HostBuilder的_configureHostConfigActions 、_configureAppConfigActions 或_configureServicesActions 中了。然后我们向HostBuilder中注入了依赖服务配置逻辑(在这个逻辑中我们将web服务GenericWebHostService添加到依赖容器中)。我们看一下GenericWebHostBuilder的构造函数:
在这里插入图片描述
通过上图,我们应该对“ GenericWebHostBuilder其实就是封装了通用主机构造者对象(HostBuilder) ”这句话有了直观的理解了。
我们将上面调试代码的执行逻辑梳理一下:
在这里插入图片描述
从上面图可以看到,我们在通用主机配置的时候就执行了webBuilder.UseStartup<Startup>();方法,那么我们深入这个方法看一下:

未完待续。。。
可以看到上面的代码执行过程,里面的hostBuilder其实是:GenericWebHostBuilder。
代码hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name);的作用是将应用名称存储到GenericWebHostBuilder的配置中去。
后面的代码supportsStartup.UseStartup(startupType)是关键地方,我们调试进去:
在这里插入图片描述

到了这里我们就知道了:webBuilder.UseStartup<Startup>();就是将Startup的Type值存储到主机构建者属性里面,并且将Startup的调用逻辑注入到了主机的Build过程中(注入到逻辑容器:_configureServicesActions),至此,我们应该明白了web服务以及Startup是怎么注入到通用主机中的了。

3.2 Startup是怎么被实例化的?

接着上面的代码调试,我们进入HostBuilder的Build()方法:
在这里插入图片描述
可以看到,在创建依赖容器之前,代码调用了一系列的依赖容器构建逻辑,而这些逻辑中就有上面注入的逻辑(Startup的实例化和ConfigureServices方法调用),那么让我们转到这个处理逻辑:
在这里插入图片描述
从上图中看到了Startup的创建和调用,接下来我们先看看Startup具体是怎么构建的,首先看代码:

instance = ActivatorUtilities.CreateInstance(new GenericWebHostBuilder.HostServiceProvider(webHostBuilderContext), startupType, Array.Empty<object>());

这里的HostServiceProvider是一个内部类,它继承自IServiceProvider,它的代码如下:
在这里插入图片描述
可以看到,这个HostServiceProvider仅提供了主机环境和配置,其他的不提供(因为到现在为止主机的依赖容器还未创建完成),所以我们在Startup的构造函数中的写法:

public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
    Configuration = configuration;
    WebHostEnvironment = webHostEnvironment;
}

public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }

因为此时主机的依赖容器并没有创建完成,所以你能在构造函数中获取到的只有:
1.环境: IHostingEnvironment、IWebHostEnvironment、IHostEnvironment,无论你写它们中的哪个,获取到的都是同一个对象
2.配置: IConfiguration
下面的写法是不正确的: 在这里插入图片描述

3.3 Startup的ConfigureServices方法是怎么被调用的?

接着上面,我们再看看ConfigureServices方法的调用,它的调用比较直接,看源码:
在这里插入图片描述
上图中显示:在调用ConfigureServices的时候直接传入了一个参数IServiceCollection services,所以我们实际调用的时候最多只能传入这个参数,传入其他的参数肯定报错(不传入也是可以的,调用代码时已经做了处理): 在这里插入图片描述
那么通过上面的分析,我们知道了Startup的创建和ConfigureServices方法的调用都是在主机Build过程中的,那么Startup的Configure方法是什么时候被调用的呢?

3.4 Startuo的Configure方法是什么时候被调用的?

还记得上面分析webBuilder.UseStartup<Startup>();的时候我们看到了怎样实例化Startup以及怎样调用ConfigureServices方法的吗,其实那段代码下面就是处理Configure方法的:
在这里插入图片描述
从上图中看出,在Host的Build过程中将Startup的Configure方法的调用配置到了web服务上的配置选项上(GenericWebHostServiceOptions),那么我们来观察下web服务是怎样被创建的,这个Configure又是怎么被调用的?
这个时候我们需要调试到Host的Run方法了,一路跟下去: 在这里插入图片描述
从上面看到在Host执行Run的时候IHostedService被依赖容器创建,那么直接看它的构造函数(GenericWebHostService): 在这里插入图片描述
在上面的构造函数中,代码this.Options = options.Value;将会触发创建GenericWebHostServiceOptions实例,进而触发之前的配置,如下图:
在这里插入图片描述
进而执行到configureBuilder.Build(instance)(app);,进而调用了Startup的Configure方法。
注意: Configure被调用时主机的依赖容器已经创建完成,所以可以在Configure方法里向依赖容器获取数据。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackletter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值