The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

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

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

aspnetcore-generic-host-deprecated

早期的想法

.NET Generic Host 是從.NET Core 2.1 開始出現的概念,主要用在 ASP.NET CoreWorker Service (背景服務) 這類需要運行在長時間執行的應用程式中,提供一組一致性極高的 API 介面,方便開發人員在不同應用程式之間進行順利的切換。不過後來發現,使用 .NET Generic Host 在不同的應用程式之間,會有一些差異,這些差異導致開發人員需要透過 Callback 的方式定義一些服務,這樣一來,就會讓程式碼變得複雜,而且不容易閱讀。

當年我還有寫過一篇使用 .NET Generic Host 建立 Console 主控台應用程式 (.NET Core 3.1+)文章。

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

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

  • ASP.NET Core Web API ( .NET 5 之前 )

    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 之前 )

    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 之後 )

    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 之後 )

    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 開始,改用以下寫法:

var builder = WebApplication.CreateBuilder(args);

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

例如,前幾年當 ASP.NET Core 6.0 剛推出的時候,因為 Serilog.AspNetCore 套件遲遲沒有更新,所以當時的官網文件是這樣寫的:

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

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

var builder = WebApplication.CreateBuilder(args);

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

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

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

var builder = WebApplication.CreateBuilder(args);

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

不過,新版的 Serilog.AspNetCore 套件已經都更新了,建議直接用標準的 builder.Services 來註冊即可,如下:

var builder = WebApplication.CreateBuilder(args);

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

直覺多了!👍

相關連結

留言評論