昨天介紹了只用 50 行的 Program.cs 程式寫出一個支援 CORS 上傳案的 ASP.NET Core 服務,下一步我想把它轉成 Windows Service 常駐執行。
(註:由留言我才知道這種極簡風網站寫法有個專有名詞叫 Minimal APIs,跟 NancyFx 一樣是我的菜,感謝讀者 Joker 分享)

官方文件有篇 Host ASP.NET Core in a Windows Service 介紹如何將 ASP.NET Core 輕鬆轉成 Windows Service。做法很簡單,專案參照 Microsoft.Extensions.Hosting.WindowsServices 套件,呼叫 Host.CreateDefaultBuilder(args).UseWindowsService(),之後就能像使用 .NET 6 開發 Windows Service文章說的,用 SC 指令或 PowerShell New-Service、Start-Service、Stop-Service 註冊、啟動及停止服務。
(安全提醒: sc create ... 註冊若未指定身分,預設會以 LocalSystem 本機高權限帳號執行,請留意你的服務是否有被惡意利用的可能,若有,請務必強化防護及換成權利較小的帳號,這些細節原本 IIS 會協助打理,轉成服務時要自己負責。延伸閱讀:豆知識:Local System、Local Service與Network Service)

不過官方文件有點簡要且沒更新到 .NET 6,我在實作過程式遇到一些小問題,摸索了一下才做出來,故寫篇筆記分享。

第一個問題,文件裡 UseWindowsService() 的寫法是:

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

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            //...

已不適用 ASP.NET Core 6 的 Top-Level Statements, 在 .NET 6 這部分簡化為 var builder = WebApplication.CreateBuilder(args),故要改為:

using Microsoft.Extensions.Hosting.WindowsServices;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWindowsService();

不過,雖然可以順利編譯並用 sc create 註冊,但 sc start 時發生無法啟動服務的 1053 錯誤:

原因跟 ContentRootPath 有關,以 Windows Service 形式啟動時,工作資料夾預設為 C:\WINDOWS\system32,導致錯誤。從事件檢視器也可以看到更多訊息:

依照錯誤訊息提示,改為自訂 WebApplicationOptions 參數物件,透過 WindowsServiceHelpers.IsWindowsService() 偵測為 Windows Service 模式時將 ContentRootPath 設為 AppContext.BaseDirectory,一般執行設 null。參考

using Microsoft.Extensions.Hosting.WindowsServices;

var webApOpts = new WebApplicationOptions {
    ContentRootPath = WindowsServiceHelpers.IsWindowsService() ?
        AppContext.BaseDirectory : default,
    Args = args
};
var builder = WebApplication.CreateBuilder(webApOpts);
builder.Host.UseWindowsService();

這樣就能順利將 ASP.NET Core 轉為 Windows Service 囉~

Tutorial of how to convert a ASP.NET Core 6 to Windows Service.


Comments

# by dash

This is useful, NICE! :)

Post a comment