5

打造極簡式 ASP.NET Core 桌面小工具 - 動態 Port 與啟動瀏覽器

 2 years ago
source link: https://blog.darkthread.net/blog/minapi-dyna-port/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
打造極簡式 ASP.NET Core 桌面小工具

對於網頁開發者來說,開發桌面小工具寫成網頁再轉桌面應用程式是最省時省力的選擇,而 Github 開發的 Electron 則是最流行的網頁轉桌面應用程式框架,大家日常使用的軟體中有許多就是用 Electron 開發的,例如:Discord、Microsoft Teams、Skype、Slack、Whatsapp,而這幾年橫掃開發界的 Visual Studio Code 更是其中經典。

對 ASP.NET MVC/ASP.NET Core 開發者來說,則有 Eletron.NET 能快速整合 Electron 與 ASP.NET Core,輕鬆將 ASP.NET Core 包成 Electron 應用程式。關於 Electron.NET,在去年有寫過文章介紹,有興趣的同學可參考:用 ASP.NET Core 寫桌面 GUI 應用程式 - Electron.NET

Electron 雖然威力強大,對我來說卻過於繁瑣笨重,開發需下載安裝 Electron CLI、額外設定,而發佈檔案因包含 Chromium,容量往往達到數百 MB,這個大小對 Teams、Slack、VSCode 等中大型應用程式仍算合理,若拿來寫批次改檔名、加解密之類的「小」工具就顯荒謬,也違背我追求的極簡風格。

見識過 ASP.NET Core Minimal API 的輕巧,我心生一念,何不用 ASP.NET Core 專案內嵌 HTML、.js 跑介面、呼叫 Minimal API MapPost("...") 寫 WebAPI,再呼叫客戶端一定有的瀏覽器開啟我們的網站,靠一個小小 EXE 搞定所有事情,這才符合極簡主義。

這個構想要實作不難,這篇先談如何動態決定 HTTP Port 並啟動瀏覽器。

ASP.NET Core 編譯發行的 exe 檔,執行時 Kestrel 預設會聽 http://localhost:5000https://localhost:5001,而我們也可透過 ASPNETCORE_URLS 環境變數、--urls 啟動參數、appsettings.json urls 設定以及 UseUrls() 方法指定 Port 參考,但小工具在客戶環境不需安裝,執行時無法確保 Port 未佔用故難以預先指定,甚至可能同一程式跑兩份,若預先指定則執行者的便會因 Port 被佔用無法繫結出錯:

因此,較理想做法是隨機挑選當時可用的 TCP Port。很幸運地,Ketrel 也有內建支援動態決定 Port,做法很簡單,只需將 Port 指定為 0 即可

小改程式,加上 app.Urls.Add("http://127.0.0.1:0"),Kestrel 便會在啟動時自動找到可用 Port,同一支程式跑兩次也不會打架:

由於 Port 隨機決定,啟動瀏覽器時需用點技巧查詢該次使用的 Port 以決定 URL。要啟動預設瀏覽器,若只考慮 Windows,用 Process.Start 呼叫 cmd.exe /c start url 即可。Kestrel URL 需在 app.Run() 啟動後透過 IServer.Features.Get<IServerAddressesFeature>() 取得,所以我們改寫成非同步 RunAsync(),取得 URL 啟動瀏覽器,再等待 RunAsync() 傳回的 Task 結束。另外,由於 VSCode 或 Visual Studio F5 偵錯時本來就會啟動瀏覽器,為避免重複,我加上 Debugger.IsAttached 檢查,只在獨立執行時啟動瀏覽器。

完整範例如下:

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Diagnostics;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Urls.Add("http://127.0.0.1:0");
app.MapGet("/", () => "Hello World!");

var task = app.RunAsync();

if (!Debugger.IsAttached)
{
    var url = app.Services.GetRequiredService<IServer>()
        .Features.Get<IServerAddressesFeature>()!
            .Addresses.First();
    Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")
    { CreateNoWindow = true });
}

task.Wait();

測試成功。

下一篇,來談談怎麼在瀏覽器關閉時結束 ASP.NET Core 程式。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK