﻿# 從 .NET 7 開始就不鼓勵使用 .NET Generic Host 建立應用程式

這應該算是 .NET Core 的歷史包袱吧，任何軟體都一樣，沒有人可以預料未來的需求，所以當初設計的時候，多多少少一定會遇到一些不合理的地方。然而，因為當年 .NET Core 算是有承諾「絕對不會」有破壞性的 API 變更，所以一些早期設計不當的 API 全部都留下來了，也因為這樣，.NET 就需要設計一些新的 API 來取代「包裹」舊的 API，雖然會帶來一些困擾，但是只對老人困擾而已，對新人來說，這些都是「新」的 API，所以不會有任何問題。這篇文章我要來介紹 .NET 7 / 8 通常該如何初始化應用程式，以及跟舊版有什麼不一樣的地方。

![aspnetcore-generic-host-deprecated](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8216659fa3a64b0593ec-421237b0-408f-413f-9f37-eb489f58ba5d.webp)

### 早期的想法

[.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?WT.mc_id=DT-MVP-4015686) 是從.NET Core 2.1 開始出現的概念，主要用在 `ASP.NET Core` 或 `Worker Service` (背景服務) 這類需要運行在**長時間執行**的應用程式中，提供一組一致性極高的 API 介面，方便開發人員在不同應用程式之間進行順利的切換。不過後來發現，使用 `.NET Generic Host` 在不同的應用程式之間，會有一些差異，這些差異導致開發人員需要透過 Callback 的方式定義一些服務，這樣一來，就會讓程式碼變得複雜，而且不容易閱讀。

> 當年我還有寫過一篇[使用 .NET Generic Host 建立 Console 主控台應用程式 (.NET Core 3.1+)](/post/2020/12/08/NET-Generic-Host-Build-Console-App)文章。

大家可以從[這篇留言](https://github.com/dotnet/runtime/discussions/81090)得知，微軟的 ASP.NET Core 架構師 [David Fowler](https://github.com/davidfowl) 提到，從 .NET 6 開始，未來的專案範本都將不採用 `.NET Generic Host` 來建立應用程式，而是改用依據情境所設計的靜態類別來建立初始物件，所以不會再堅持統一使用 `IHostBuilder` 這個介面來建立所有的應用程式。

我們先來看看早期應用程式啟動方式，基本上都是透過 `Host.` 開始的：

-   ASP.NET Core Web API ( .NET 5 之前 )
    
    ```csharp
    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 是 IWebHostBuilder 介面
                    webBuilder.UseStartup<Startup>();
                });
    }
    ```
    
-   Worker Service ( .NET 5 之前 )
    
    ```csharp
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    // 注意: 這裡的 services 是 IServiceCollection 介面，用法不太一致
                    services.AddHostedService<Worker>();
                });
    }
    ```
    

由於微軟從客戶端與內部都發現，使用 Callback 並不是很容易閱讀，而且不容易測試，所以微軟打從 .NET 6 開始，就決定不再鼓勵 `.NET Generic Host` 這種用法。也因為這樣，從 ASP.NET Core 6.0 開始就看不到 `Startup` 這個類別了，他並不是不能用，只是不鼓勵繼續這樣用而已。

### 全新的用法

從 .NET 7 開始，微軟推出了一個新的 API，叫做 `Host.CreateApplicationBuilder()`，主要給 `non-Web` (非網站) 類型的應用程式使用，藉此切割跟 Web 網站類型的應用程式，讓 API 的使用更加一致。

所以，新的寫法如下：

-   ASP.NET Core Web API ( .NET 6 之後 )
    
    ```csharp
    var builder = WebApplication.CreateBuilder();
    
    builder.Logging.AddConsole();
    
    builder.Services.AddOptions<MyOptions>().BindConfiguration("MyConfig");
    
    builder.Services.AddHostedService<MyWorker>();
    
    var app = builder.Build();
    
    app.MapGet("/", () => "Hello World");
    
    app.Run();
    ```
    
-   Worker Service ( .NET 7 之後 )
    
    ```csharp
    var builder = Host.CreateApplicationBuilder();
    
    builder.Logging.AddConsole();
    
    builder.Services.AddOptions<MyOptions>().BindConfiguration("MyConfig");
    
    builder.Services.AddHostedService<MyWorker>();
    
    var host = builder.Build();
    
    host.Run();
    ```
    

各位看官應該可以發現，這組新的 API 寫法，就只有「第一行」不一樣而已，其他行的用法都完全一致，而且完全看不到 Callback 的語法，不但簡化了寫法，初學者更不容易寫錯，我認為這樣的設計確實相當不錯！👍

### 過渡期的處理方法

因為從 ASP.NET Core 6.0 開始，改用以下寫法：

```csharp
var builder = WebApplication.CreateBuilder(args);
```

但是這裡的 `builder` 其實是 `WebApplicationBuilder` 型別（因為用 `var` 看不出來型別)，如果有老舊套件還是只認得 `IWebHostBuilder` 怎麼辦呢？微軟的解決方法是透過 `builder.WebHost` 這個屬性，取得 `IWebHostBuilder` 型別的物件，這樣就可以繼續使用舊的套件了。

例如，前幾年當 ASP.NET Core 6.0 剛推出的時候，因為 [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore/) 套件遲遲沒有更新，所以當時的官網文件是這樣寫的：

```csharp
public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog() // <-- Add this line
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
```

如果當時 ASP.NET Core 6.0 要用的話，就要改寫法如下：

```csharp
var builder = WebApplication.CreateBuilder(args);

// 這裡的 builder.Host 會得到一個 IHostBuilder 介面
builder.Host.UseSerilog(); // <-- Add this line
```

> 上面的寫法是在 `Serilog.Extensions.Hosting` 套件中有實作實作 `IHostBuilder.UseSerilog()` 擴充方法，所以可以這樣寫。

以下這個寫法也可以，因為在 `Serilog.AspNetCore` 套件中剛好有實作 `IWebHostBuilder.UseSerilog()` 擴充方法：

```csharp
var builder = WebApplication.CreateBuilder(args);

// 這裡的 builder.WebHost 會得到一個 IWebHostBuilder 介面
// 這是 ASP.NET Core 6.0 特別為了跟舊版套件相容所特別揭露的
builder.WebHost.UseSerilog(); // <-- Add this line
```

不過，新版的 [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore/) 套件已經都更新了，建議直接用標準的 `builder.Services` 來註冊即可，如下：

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSerilog(); // <-- Add this line
```

直覺多了！👍

### 相關連結

-   ['Host.CreateDefaultBuilder' vs 'WebApplication.CreateBuilder' API shape inconsistency · dotnet/runtime · Discussion #81090](https://github.com/dotnet/runtime/discussions/81090)
-   ["Minimal hosting" for ASP.NET Core applications #30354](https://github.com/dotnet/aspnetcore/issues/30354)
-   Microsoft Learn
    -   [.NET Generic Host](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host?tabs=appbuilder&WT.mc_id=DT-MVP-4015686)
    -   [ASP.NET Core Web Host](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?WT.mc_id=DT-MVP-4015686)
    -   [.NET Generic Host in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?WT.mc_id=DT-MVP-4015686)
    -   [WebApplication and WebApplicationBuilder in Minimal API apps](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/webapplication?WT.mc_id=DT-MVP-4015686)
