這應該算是 .NET Core 的歷史包袱吧,任何軟體都一樣,沒有人可以預料未來的需求,所以當初設計的時候,多多少少一定會遇到一些不合理的地方。然而,因為當年 .NET Core 算是有承諾「絕對不會」有破壞性的 API 變更,所以一些早期設計不當的 API 全部都留下來了,也因為這樣,.NET 就需要設計一些新的 API 來取代「包裹」舊的 API,雖然會帶來一些困擾,但是只對老人困擾而已,對新人來說,這些都是「新」的 API,所以不會有任何問題。這篇文章我要來介紹 .NET 7 / 8 通常該如何初始化應用程式,以及跟舊版有什麼不一樣的地方。
早期的想法
.NET Generic Host 是從.NET Core 2.1 開始出現的概念,主要用在 ASP.NET Core
或 Worker 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
直覺多了!👍
相關連結