寫 ASP.NET Core 時,我超愛用 dotnet watch 功能,.NET 會在網頁注入一段 JavaScript,一旦偵測到專案範圍的檔案異動,便會使用熱重載(Hot Reload)技術動態將異動結果套用到執行中的應用程式並重新載入網頁,能即時看到修改結果就是爽! 如果只是更新 .html、.js、.css 等靜態檔案自動重載看結果沒什麼了不起,但更新 .cs 不必編譯整個應用程式重新啟動,居然針對局部套用,堪稱黑魔法。(註:這個神奇功能始於 .NET 6)

但我使用 dotnet watch 測試常遇到一個小困擾,用個範例重現問題。

假設我有個 HTML 如下,網頁上有顆按鈕,按下會呼叫 AJAX 寫入資料到 ContentRoot ./data/data.txt 檔案,並在 <pre> 顯示一行訊息。理論上,這個網頁可連按多次,連續更新 ./data/data.txt 並在 <pre> 印出多行訊息:

<!DOCTYPE html>
<html>
    <head>
        <title>dotnet watch run Test</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <button onclick="updateServerFile()">Update Server File</button>
        <pre id="msgs"></pre>
        <script>
            function appendMessage(msg) {
                const pre = document.getElementById('msgs');
                pre.textContent += msg + '\n';
            }
            function updateServerFile() {
                fetch('/update-server-file', { method: 'POST' })
                    .then(response => {
                        if (response.ok) {
                            appendMessage('Update at ' + new Date().toLocaleTimeString('sv'));
                        }
                    });
            }
        </script>
    </body>
</html>

Program.cs 的寫法如下,程式很短邏輯很單純不值一提:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var indexHtmlPath = Path.Combine(app.Environment.ContentRootPath, "index.html");
app.MapGet("/", () => Results.Text(File.ReadAllText(indexHtmlPath), "text/html"));

var dataFolder = Path.Combine(app.Environment.ContentRootPath, "data");
if (!Directory.Exists(dataFolder)) Directory.CreateDirectory(dataFolder);
var dataPath = Path.Combine(dataFolder, "data.txt");

app.MapPost("/update-server-file", () => 
{
    File.WriteAllText(dataPath, DateTime.Now.ToString());
    return Results.Ok("Server file updated.");
});
app.Run();

dotnet run 測試,一切如預期:

但改用 dotnet watch 測試,會發現按鈕只能按一次,畫面就會變灰。原因是寫入 ./data/data.txt 動作會觸發檔案異動偵測,引起網頁重載,打亂了我們的測試計劃。

爬文查到幾種做法,像是在 .csproj 加入以下宣告:

<ItemGroup>
    <Watch Remove="data\**\*" />
</ItemGroup>
<!-- 或是 -->
<ItemGroup>
    <Content Update="data\**\*" Watch="false" />
</ItemGroup>

但我實測都沒成功,最後找到有效的做法是這個:

<PropertyGroup>
    <DefaultItemExcludes>$(DefaultItemExcludes);data/**</DefaultItemExcludes>
</PropertyGroup>

Explains how dotnet watch triggers unwanted reloads when updating data files, and how to exclude folders (e.g., data/**) to avoid this issue.


Comments

Be the first to post a comment

Post a comment