.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方法裏向依賴容器獲取數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章