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

来源:http://www.cnblogs.com/RainingNight/archive/2017/08/24/hosting-in-asp-net-core.html
-Advertisement-
Play Games

ASP.NET Core 是新一代的 ASP.NET,第一次出現時代號為 ASP.NET vNext,後來命名為ASP.NET 5,隨著它的完善與成熟,最終命名為 ASP.NET Core,表明它不是 ASP.NET 的升級,而是一個重新設計的Web開發框架。而它一個非常重要的變化就是它不再依賴於I ...


ASP.NET Core 是新一代的 ASP.NET,第一次出現時代號為 ASP.NET vNext,後來命名為ASP.NET 5,隨著它的完善與成熟,最終命名為 ASP.NET Core,表明它不是 ASP.NET 的升級,而是一個重新設計的Web開發框架。而它一個非常重要的變化就是它不再依賴於IIS,而是一個獨立的自寄宿的控制台應用程式,這也是它可以跨平臺的基石,而本文就來詳細探討一下 ASP.NET Core 的啟動過程。

前言

我們先回顧一下以前的 ASP.NET 是怎麼來運行的:

IISClassicAspNet

ASP.NET 是嚴重依賴於IIS的,System.Web 中有很多方法都是直接調用的 IIS API,並且它還是駐留在IIS進程中的。而 ASP.NET Core 的運行則是一個完全獨立的控制台程式,它有自己的 Kestrel Server,可以直接對外部提供服務。

不過 Kestrel 的功能相對較於簡單,所以我們還是需要一個反向代理伺服器將 Kestrel 伺服器保護起來。而微軟也為我們提供了 UseIISIntegration 方法,方便與IIS進行集成。因此,在 Windows 下,通常還是使用IIS來部署,那麼,此時與 ASP.NET 的運行方式又有什麼區別呢?

IISHosting

通過上圖,可以很清楚的明白它們的區別。在 ASP.NET Core 中,IIS 是通過 HTTP 的方式來調用我們的 ASP.NET Core 程式。而部署在IIS中時,並不需要我們手動來啟動 ASP.NET Core 的控制台程式,這是因為IIS新增了一個 AspNetCoreModule 模塊,它負責 ASP.NET Core 程式的啟動與停止,並能監聽 ASP.NET Core 程式的狀態,在我們的應用程式意外崩潰時重新啟動。

下麵開始進入正題,進入到 ASP.NET Core 的代碼中去。

WebHost的創建

對於一個程式控制台程式來說,它的入口點便是 Program 中的 Main 方法,ASP.NET Core 程式自然也不例外:

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

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

WebHost.CreateDefaultBuilder 是在 2.0 中新增的,只是用來簡化我們的代碼,其內部實現與 1.0 中的類似,主要做了6件事:

  1. 註冊 Kestrel 中間件,指定 WebHost 要使用的 Server(HTTP伺服器)。
  2. 設置 Content 根目錄,將當前項目的根目錄作為 ContentRoot 的目錄。
  3. 讀取 appsettinggs.json 配置文件,開發環境下的 UserSecrets 以及環境變數和命令行參數。
  4. 讀取配置文件中的 Logging 節點,對日誌系統進行配置。
  5. 添加 IISIntegration 中間件。
  6. 設置開發環境下, ServiceProviderValidateScopestrue,避免直接在 Configure 方法中獲取 Scope 實例。

然後指定 Startup 類,最後通過 Build 方法構建出 WebHostWebHostBuilder 的代碼較多,感興趣的可以去看完整代碼: WebHostBuilder,而我在這裡只展示部分代碼片段來幫助理解:

public IWebHost Build()
{
    var hostingServices = BuildCommonServices(out var hostingStartupErrors);
    var applicationServices = hostingServices.Clone();
    var hostingServiceProvider = hostingServices.BuildServiceProvider();

    AddApplicationServices(applicationServices, hostingServiceProvider);
}

Build 中的 BuildCommonServices 方法主要有兩個功能:

首先在程式集中查找 HostingStartupAttribute:

if (!_options.PreventHostingStartup)
{
    var exceptions = new List<Exception>();

    // Execute the hosting startup assemblies
    foreach (var assemblyName in _options.HostingStartupAssemblies)
    {
        var assembly = Assembly.Load(new AssemblyName(assemblyName));

        foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
        {
            var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
            hostingStartup.Configure(this);
        }
    }
}

HostingStartupAttribute 給我們一個在其它程式集中做一些啟動配置的機會,在我們進行多層開發及模塊化的時候非常有用,後面會再詳細解釋。

然後便是查找我們的 Startup 類:

if (!string.IsNullOrEmpty(_options.StartupAssembly))
{
    var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);

    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
    {
        services.AddSingleton(typeof(IStartup), startupType);
    }
    else
    {
        services.AddSingleton(typeof(IStartup), sp =>
        {
            var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
            var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
            return new ConventionBasedStartup(methods);
        });
    }
}

首先是判斷是否有 _options.StartupAssembly,對應配置文件中的 "startupAssembly" ,如果我們沒有設置,那便是空的,並不會執行上面代碼。通常我們會使用 UseStartup<Startup> 的方法來註冊 Startup 類,而他們的作用是一樣的,都是將我們的 Startup 類做為一個單例註冊到了 DI 系統。

而最終 BuildCommonServices 返回一個 IServiceCollection,用於構建 hostingServiceProvider

var hostingServices = BuildCommonServices(out var hostingStartupErrors);
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();

接下來創建 WebHost :

public IWebHost Build()
{
    var host = new WebHost(
        applicationServices,
        hostingServiceProvider,
        _options,
        _config,
        hostingStartupErrors);
    }
    
    host.Initialize();

    return host;
}

這裡需要說明的,hostingServiceProvider 是 ASP.NET Core 中的第一個 ServiceProvider,也是根 ServiceProvider,但它是在我們的 Starpup 類執行之前創建的,也就是說並不會包含我們在 ConfigureServices 中註冊的服務(但包含使用 HostingStartupAttribute 註冊的服務)。

WebHost啟動流程

在上一步,創建完 WebHost 之後,便調用它的 Run 方法,而 Run 方法再去調用 WebHostStartAsync 方法,開始 ASP.NET Core 的啟動工作,主要包含以下幾個步驟:

1. 初始化,構建 RequestDelegate

RequestDelegate 是我們的應用程式處理請求,輸出響應的整個過程,也就是我們的 ASP.NET Core 請求管道。

而它有如下定義:

public delegate Task RequestDelegate(HttpContext context);

這裡不再對 RequestDelegate 進行過多的介紹,以後會詳細解釋。

1.1. 調用 Startup 中的 ConfigureServices 方法

在前面介紹過,我們的 Startup 類已經註冊到了 ASP.NET Coer 的 DI 系統中,因此可以直接從 DI 中獲取:

private IStartup _startup;
private IServiceProvider _applicationServices;

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

這裡使用的 _hostingServiceProvider 是我們在 WebHost 中創建的根 ServieProvider

1.2. 初始化 Http Server

Server 是一個HTTP伺服器,負責HTTP的監聽,接收一組 FeatureCollection 類型的原始請求,並將其包裝成 HttpContext 以供我們的應用程式完成響應的處理。

public interface IServer : IDisposable
{
    IFeatureCollection Features { get; }

    Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

而上面註冊的 Kestrel 便是預設的 Server

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
    hostBuilder.UseLibuv();

    return hostBuilder.ConfigureServices(services =>
    {
        services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
        services.AddSingleton<IServer, KestrelServer>();
    });
}

Server的初始化主要是配置要監聽的地址:

private void EnsureServer()
{
    if (Server == null)
    {
        Server = _applicationServices.GetRequiredService<IServer>();

        var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
        var addresses = serverAddressesFeature?.Addresses;
        if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
        {
            var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
            if (!string.IsNullOrEmpty(urls))
            {
                serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey);

                foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    addresses.Add(value);
                }
            }
        }
    }
}

Addresses 預設是通過在 launchSettings.json 中來查找的。

1.3. 創建 IApplicationBuilder

IApplicationBuilder 用於構建應用程式的請求管道,也就是生成 RequestDelegate,有如下定義:

public interface IApplicationBuilder
{
    IServiceProvider ApplicationServices { get; set; }

    IFeatureCollection ServerFeatures { get; }

    IDictionary<string, object> Properties { get; }

    RequestDelegate Build();

    IApplicationBuilder New();

    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

而它的創建過程是通過 ApplicationBuilderFactory 來創建的:

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

IApplicationBuilderFactory 的預設實現 ApplicationBuilderFactory:

public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
{
    return new ApplicationBuilder(_serviceProvider, serverFeatures);
}

ApplicationBuilder 的實現方式就不在這裡多說了,在講中間件的時候再來細說。

1.4. 配置 IApplicationBuilder

我們比較的熟悉的是在 Startup 類的 Configure 方法中對 IApplicationBuilder 進行配置,其實還有一個 IStartupFilter 也可以用來配置 IApplicationBuilder,並且在 Startup 類的Configure 方法之前執行:

var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
    configure = filter.Configure(configure);
}

configure(builder);

然後調用 IApplicationBuilderBuild 方法,便完成了 RequestDelegate 的創建:

 private RequestDelegate BuildApplication()
{
    ...

    return builder.Build();
}

2. 啟動 Server,監聽請求並響應

Server 本身是並不清楚 HttpContext 的細節的,因此它需要接收一個 IHttpApplication 類型的參數,來負責 HttpContext 的創建,由如下定義:

public interface IHttpApplication<TContext>
{
    TContext CreateContext(IFeatureCollection contextFeatures);

    Task ProcessRequestAsync(TContext context);

    void DisposeContext(TContext context, Exception exception);
}

它的預設實現是 HostingApplication 類,而 ProcessRequestAsync 方法則調用我們上面創建的 RequestDelegate 委托,來完成對 HttpContext 的處理:

public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
    private readonly RequestDelegate _application;

    public Task ProcessRequestAsync(Context context)
    {
        return _application(context.HttpContext);
    }
}

最後啟動 Server:

var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);

await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);

Server 會綁定一個監聽埠,註冊HTTP連接事件,最終交給 Http2Stream<TContext> 來處理,通過上面的 hostingApp 來切入到我們的應用程式中,完成整個請求的處理:

public class Http2Stream<TContext> : Http2Stream
{
    private readonly IHttpApplication<TContext> _application;

    public override async Task ProcessRequestAsync()
    {
        ...

        var context = _application.CreateContext(this);

        try
        {
            await _application.ProcessRequestAsync(context);

            ...
        }
        finally
        {
            _application.DisposeContext(context, _applicationException);

            ...
        }
        ...
    }
}

3. 啟動 HostedService

HostedService 為我們提供一個註冊後臺運行服務的機會,它會在隨著我們的 ASP.NET Core 程式啟動而啟動,併在 ASP.NET Core 停止時進行優雅的關閉,有如下定義:

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

而它是通過 HostedServiceExecutor 來執行的:

public class HostedServiceExecutor
{
    private readonly IEnumerable<IHostedService> _services;

    public async Task StartAsync(CancellationToken token)
    {
        await ExecuteAsync(service => service.StartAsync(token));
    }

    public async Task StopAsync(CancellationToken token)
    {
        await ExecuteAsync(service => service.StopAsync(token));
    }

    private async Task ExecuteAsync(Func<IHostedService, Task> callback)
    {
        foreach (var service in _services)
        {
            await callback(service);
        }
    }
}

WebHost 會調用 HostedServiceExecutorStartAsync ,從而完成對 HostedService 的啟動:

_applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();

// Fire IApplicationLifetime.Started
_applicationLifetime?.NotifyStarted();

// Fire IHostedService.Start
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);

這裡還有對 IApplicationLifetime 啟動事件的觸發,以後會介紹一下 IApplicationLifetime 的用途。

到此 WebHost 的整個啟動過程介紹完畢。

總結

本文粗略地介紹了一下 ASP.NET Core 中 WebHost 創建及啟動,它也是 ASP.NET Core 中的宿主,包含 HttpServer 的啟動與監聽,而其中也涉及到了很多關鍵點,對我們以後的開發非常有用,由於篇幅有限,下一章再來介紹一些本文沒有解釋清楚的概念。

參考文章:

Publishing-and-Running-ASPNET-Core-Applications-with-IIS


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本節課程,需要完成擴增子分析解讀1質控 實驗設計 雙端序列合併 先看一下擴增子分析的整體流程,從下向上逐層分析 分析前準備 # 進入工作目錄 cd example_PE250 上一節回顧:我們拿到了雙端數據,進行了質控、並對實驗設計進行了填寫和檢查、最後將雙端數據合併為單個文件進行下游分析。 接下來 ...
  • 對值類型和引用類型的誤解 (引用類型存儲在堆上,值類型存儲在棧上) 對值類型和引用類型的誤解 (引用類型存儲在堆上,值類型存儲在棧上) 在學習C#基礎篇幅的時候總是逃不掉值類型和引用類型,很多新手包括我以前對它的理解也只是停留在"引用類型存儲在堆上,值類型存儲在棧上". 這個誤區主要歸咎於我們根本沒 ...
  • 第 9 章:常用的設計模式 9.1 聚合組件 考慮為常用的特性域提供聚合組件。 要用聚合組件來對高層的概念(物理對象)進行建模,而不是對系統級的任務進行建模。 要讓聚合組件的名字與眾所周知的系統實體相對應,比如 MessageQueue、Process 或 EventLog,這樣就能使類型更加引人註 ...
  • 為什麼有這種需求, 原因是這樣的, 公司有一個Java的web項目,在另一臺伺服器A上,最近老闆一時興起,想把他合併到這台穩定點的伺服器B上,伺服器B上使用IIS來寄宿asp.net 網站, 怎麼辦呢,硬著頭皮上吧,在網上找各種解決方案: 解決方案一:isapi_redirect 這個方法按照方法試 ...
  • 在網路編程過程中需要向伺服器上傳文件。 Multipart/form-data是上傳文件的一種方式。 ...
  • 創建和開發ASP.NET Core應用可以有二種方式:最簡單的方式是通過visual studio 2017 來創建,其優點是簡單方便,但需要安裝最新版本visual studio 2017 preview 15.3 。另一種方式是使用visual studio code來創建,vscode 則是一... ...
  • 微信分享代碼,先引入: 獲取簽名: 分享代碼: wxsign輸出的json ...
  • 1、導出Excel : 首先引用NPOI包(Action一定要用FileResult) /// <summary> /// 批量導出需要導出的列表 /// </summary> /// <returns></returns> public FileResult ExportStu2() { //獲取 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...