.NET8 WebApplication剖析

xiaolipro發表於2023-11-05

WebApplication 是用於配置HTTP管道和路由的web應用程式,接來下我將一一拆解它的組成。

/// <summary>
/// The web application used to configure the HTTP pipeline, and routes.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
[DebuggerTypeProxy(typeof (WebApplication.WebApplicationDebugView))]
public sealed class WebApplication : IHost,IDisposable,IApplicationBuilder,IEndpointRouteBuilder,IAsyncDisposable

IHost

​ 首先Web應用是一個程式,而 IHost 就是程式的抽象

public interface IHost : IDisposable
{
  IServiceProvider Services { get; }
  Task StartAsync(CancellationToken cancellationToken = default (CancellationToken));
  Task StopAsync(CancellationToken cancellationToken = default (CancellationToken));
}

​ 一個程式具備啟動、停止生命週期,這很好理解。我要說的是 IServiceProvider ,他非常關鍵,後面會在依賴注入章節來詳細解釋。目前你只需要知道他是一個服務供應商就可以了,就可以透過他獲取想要的服務,但前提是你在IOC容器中註冊過。

​ Host StartAsync 程式碼流如下:

await host._hostLifetime.WaitForStartAsync(token1).ConfigureAwait(false); // 註冊start程式
host.Services.GetService<IStartupValidator>()?.Validate(); // 校驗
IHostedLifecycleService.StartingAsync
IHostedService.StartAsync
IHostedLifecycleService.StartedAsync
host._applicationLifetime.NotifyStarted();

StopAsync 類似,程式碼流如下:

IHostedLifecycleService.StoppingAsync
IHostedService.StopAsync
IHostedLifecycleService.StoppedAsync
this._logger.StoppedWithException((Exception) ex);

​ 值得注意的是 IStartupValidatorIHostedServiceIHostedLifecycleService 分別為我們提供不同的鉤子,只需要向容器註冊即可加入我們自定義的業務邏輯。

IApplicationBuilder

WebApplication 實現 IApplicationBuilder 具有pipeline機制。

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

​ 這裡要解釋一下pipeline,管道是.NET中非常普及的一個概念,核心是切面程式設計,同樣的在後續我們會有專門的章節來例舉它。現在你只需要知道,他是一個洋蔥模型。

​ 同時,IApplicationBuilder 從命名上就表達了這是一個構建者模式,因此 WebApplication 提供了 Build

public WebApplication Build()
{
  this._hostApplicationBuilder.Services.Add(this._genericWebHostServiceDescriptor);
  this.Host.ApplyServiceProviderFactory(this._hostApplicationBuilder);
  this._builtApplication = new WebApplication(this._hostApplicationBuilder.Build());
  return this._builtApplication;
}

​ 篇幅問題這裡不展開討論,但在Build方法中會有四個鉤子被執行

public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)

​ 最終服務容器會被標記成只讀

IEndpointRouteBuilder

IEndpointRouteBuilder為程式定義路由構建的約定。

ICollection<EndpointDataSource> DataSources { get; }

​ 這是 WebApplication 中的實現,可以看到 EndpointDataSource 的實現會被組合進來。

public IReadOnlyList<Endpoint> Endpoints
{
  get
  {
    EndpointDataSource requiredService = this._webApplication.Services.GetRequiredService<EndpointDataSource>();
    return requiredService is CompositeEndpointDataSource endpointDataSource && endpointDataSource.DataSources.Intersect<EndpointDataSource>((IEnumerable<EndpointDataSource>) this._webApplication.DataSources).Count<EndpointDataSource>() != this._webApplication.DataSources.Count ? new CompositeEndpointDataSource((IEnumerable<EndpointDataSource>) this._webApplication.DataSources).Endpoints : requiredService.Endpoints;
  }
}

EndpointDataSource實際上就是一組 Endpoint,而 Endpoint 是 AspNetCore 下極其重要的一節,同樣會在後續展開講。現在你只需要知道,它表示一個處理 HTTP 請求的終點,包含了處理請求的邏輯和相關的後設資料。

IAsyncDisposable

IAsyncDisposable 是 .NET Core 2.0 引入的一個介面,用於非同步釋放資源的模式。它是 IDisposable 介面的非同步版本。某些資源的釋放可能涉及到非同步操作,使用 IDisposable 介面的同步釋放模式可能會導致阻塞執行緒,影響應用程式的效能和響應性。

public interface IAsyncDisposable
{
  ValueTask DisposeAsync();
}

​ 在使用完該物件後,可以使用 await using語法糖或直接呼叫 ``DisposeAsync()` 方法來釋放資源。

Run

Run 函式是 WebApplication 的啟動按鈕,你可以傳遞一個url,加入到監聽列表

public void Run([StringSyntax("Uri")] string? url = null)
{
  this.Listen(url);
  // public static void Run(this IHost host) => host.RunAsync().GetAwaiter().GetResult();
  HostingAbstractionsHostExtensions.Run(this);
}

HostingAbstractionsHostExtensions.Run的原始碼相對簡單,host的 StartAsyncWaitForShutdownAsync 在上面都介紹了,WebApplicationDisposeAsync 也在finally塊中觸發

public static async Task RunAsync(this IHost host, CancellationToken token = default (CancellationToken))
{
  try
  {
    ConfiguredTaskAwaitable configuredTaskAwaitable = host.StartAsync(token).ConfigureAwait(false);
    await configuredTaskAwaitable;
    configuredTaskAwaitable = host.WaitForShutdownAsync(token).ConfigureAwait(false);
    await configuredTaskAwaitable;
  }
  finally
  {
    if (host is IAsyncDisposable asyncDisposable)
      await asyncDisposable.DisposeAsync().ConfigureAwait(false);
    else
      host.Dispose();
  }
}

​ 上面有個很有意思的點是,為什麼要在finally塊中觸發?這是因為Host的生命週期函式都用了聯合取消令牌,這是一種安全取消協作模式,在令牌取消後會觸發一個 OperationCanceledException 異常,進而在這種情況下還能夠正常處理銷燬工作,這是一種非常優秀的程式設計習慣。

相關文章