← 文章列表

ASP.NET Framework 遷移 .NET 8:Razor 建置與請求驗證指南

ASP.NET Framework 遷移 .NET 8:Razor 建置與請求驗證指南

把 ASP.NET Framework MVC 舊站遷移到 .NET 8,不應只以「方案建置成功」作為完成條件。可靠的驗收要拆成套件還原、方案建置、程序啟動與實際 HTTP 請求四道閘門,並讓每一道閘門留下可判讀的退出碼、記錄與回應狀態。本文聚焦 ASP.NET Framework 遷移 .NET 8 時最容易被忽略的 Razor View 驗證,不討論一般 API 契約、相依注入生命週期或資料庫查詢效能。

為什麼建置成功仍不足以證明 Razor 遷移完成

建置成功只能證明目前建置圖與已納入編譯的內容通過,不能代替瀏覽器會走到的路由、版面配置、局部檢視與執行期服務驗證。

舊 ASP.NET MVC 專案通常同時帶著 System.Web 思維、歷史套件版本、舊式專案檔與大量 .cshtml。遷移並不是把目標框架改成 net8.0 就結束。ASP.NET Core 有不同的主機模型、HTTP 抽象、設定來源與 Razor 編譯流程;舊 View 中看似自然的成員,不一定仍能在新基底類別或目前命名空間中使用。

建議把「完成」改寫成可觀察條件。第一,dotnet restore 得到一致的資產圖;第二,dotnet build 的退出碼為零,而且完整記錄沒有 NUMSBCS 類錯誤;第三,Web 程序真的開始監聽測試端點;第四,首頁、共用 Layout、Partial、登入前公開頁與主要錯誤頁都收到預期 HTTP 回應。這四件事彼此相關,但不能互相取代。

Razor 本身也有多個時機。官方文件指出,Razor 檔案可在建置與發佈階段編譯;開發工作流也能明確加入 Runtime Compilation 套件及服務。Runtime Compilation 適合在受控測試專案中快速看到 View 變更;它會停用 .NET Hot Reload,且官方不建議用於正式環境,因此不應被誤解為正式部署必要元件。對遷移專案而言,最重要的不是偏好哪一種模式,而是明確知道錯誤在哪一個階段被攔截,並把該階段納入驗收。

先把還原與建置錯誤分流

dotnet build 回傳非零退出碼時,先保存完整輸出,再分別檢查 NuGet、MSBuild 與 C# 類別,禁止只統計 CS 開頭的編譯錯誤。

套件降版常以 NU1605 出現。底層類別庫提升了直接或傳遞相依版本,上層 Web 專案卻固定舊版時,單獨建置底層專案可能成功,整個方案仍會失敗。修正方式不是盲目複製某組版本,而是讀取當次 restore graph,讓上層直接參考滿足圖中的最低要求,再重新還原與建置。版本要寫進中央套件管理或專案檔,讓 CI 與本機得到同一結果。

專案型別也要獨立檢查。類別庫若被誤設為可執行輸出,可能出現缺少進入點的 CS5001;原始碼使用第三方 attribute 卻沒有明確參考,則會在目前專案邊界暴露缺件。這些錯誤與 Razor 無關,卻常被混在一次大型遷移中。先讓還原與一般編譯乾淨,再處理 View,診斷訊號會清楚很多。

建議使用固定 SDK 並保留退出碼:

mkdir razor-migration-check && cd razor-migration-check
dotnet new globaljson --sdk-version 8.0.100 --roll-forward disable
dotnet new mvc -n RazorCheck -f net8.0
dotnet restore RazorCheck/RazorCheck.csproj
dotnet build RazorCheck/RazorCheck.csproj -f net8.0 --no-restore -v minimal
printf 'BUILD_EXIT=%s\n' "$?"

dotnet build 會在需要時隱含還原,但遷移診斷最好把 restorebuild --no-restore 分開執行。如此一來,套件來源、資產圖或相容性問題不會與編譯訊息混成一團。若工作環境支援 lock file,也應把鎖定模式納入測試,避免隔天解析到不同相依圖。

正確理解 Razor Build-time 與 Runtime Compilation

Razor 建置期編譯能提早攔截許多語法與符號錯誤;Runtime Compilation 則在受控開發情境中重新編譯變更的 View,但兩者都不能取代路由層級測試。

在 ASP.NET Core 8 測試專案中,可加入與目標框架相符的 Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 8.0.x 套件,並在服務註冊鏈上呼叫 AddRazorRuntimeCompilation()。固定主版本很重要:測試專案的目標框架、共享框架與套件主版本應一致,否則得到的是版本混用問題,而不是遷移問題。

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddControllersWithViews()
    .AddRazorRuntimeCompilation();

var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

不要從「簡單錯誤會在建置期被抓到」推導成「所有 View 都已可用」。View 可能依賴目前請求、ViewData、服務注入、版面配置、局部檢視、Tag Helper 或只在特定分支載入的模型。即使產生碼可編譯,實際請求仍可能在執行期間遇到空值、模型型別不符、服務未註冊或遷移後的 HTTP API 差異。因此驗收單位應是可到達的路由集合,而不是只有專案檔。

同樣地,錯誤行列要當作線索,不要當作絕對原始座標。Razor 會產生 C#,診斷有時對應到產生後內容。先保存完整例外、View 路徑與請求 URL,再搜尋舊式 RequestResponseSessionSystem.Web 用法,逐一改成 ASP.NET Core 的明確抽象。本文不提供相容 shim,因為 shim 容易掩蓋尚未確認的行為差異。

建立四道可稽核驗收閘門

最小驗收流程應依序檢查還原、建置、啟動與 HTTP 路由,每一關都要有通過條件,失敗時立即停止,不讓後續綠燈遮蔽前一關。

閘門一:套件還原。 固定 .NET 8 SDK,執行 dotnet restore,並保存完整輸出。通過條件是退出碼為零且沒有 package downgrade。若發現 NU1605,使用 dotnet list package --include-transitive 讀取直接與傳遞版本,再調整真正擁有該參考的專案。

閘門二:方案建置。 使用 dotnet build --no-restore,避免重跑還原改變診斷內容。通過條件是退出碼為零,而不是「找不到 CS 錯誤」。建置記錄應同時檢查 NUMSBCS 類訊息。若類別庫可建置、方案不可建置,就比較專案參考方向與上層直接套件版本。

閘門三:程序啟動。 在本機或容器使用迴圈端點啟動測試專案,等待主機輸出 listening 訊息,再用 HTTP 工具確認。程序存在不代表應用程式已就緒;反過來,啟動命令回傳也不代表背景程序仍存活。通過條件至少包括程序未退出與測試端點可連線。

閘門四:路由矩陣。 建立不涉及真實帳號與外部寫入的 GET 清單,涵蓋首頁、共用版面配置、至少一個 Partial、錯誤頁與匿名可讀功能頁。每個請求保存狀態碼與伺服器記錄。若頁面需要狀態,使用測試替身或記憶體資料,禁止連接正式資料來源。

閘門 主要證據 通過條件 常見誤判
還原 restore 輸出、資產圖 退出碼為零、無降版錯誤 只看底層類別庫
建置 完整 build 記錄 退出碼為零 只搜尋 CS
啟動 主機記錄、程序狀態 持續執行且端點可連線 只看程序名稱
路由 HTTP 狀態、例外記錄 預期頁面全部通過 只測首頁

驗證與重現:固定 .NET 8 沙箱測試

以下步驟只建立本機測試專案並呼叫迴圈端點,不接觸正式資料;排程環境已實際執行核心建置與 HTTP 行為驗證。

前置條件為 Windows、macOS 或 Linux 上已安裝 .NET SDK 8.0.100,能連線官方 NuGet 套件來源,並保留一個未使用的本機測試連接埠。套件固定為 Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 8.0.22,專案固定目標 net8.0

  1. 建立 MVC 測試專案與 global.json,確認 dotnet --version 精確輸出 8.0.100,再加入 Runtime Compilation 8.0.22。
  2. AddControllersWithViews() 改成串接 AddRazorRuntimeCompilation(),執行 restore 與 build;正常 View 應得到零退出碼。
  3. dotnet run --no-build --no-launch-profile --urls http://127.0.0.1:5199 啟動,對根路由送出 GET;正常 View 應回傳 HTTP 200。
  4. 在程序執行中,於 Views/Home/Index.cshtml 加入未定義 Razor 符號,再重送 GET;應觀察 HTTP 500,伺服器記錄包含 CompilationFailedExceptionCS0103
  5. 移除故意加入的錯誤並重送 GET;通過條件是回到 HTTP 200,且程序不需承擔任何外部寫入。

排程環境的真實結果如下:機器具備 .NET SDK 8.0.100;固定套件 8.0.22 可還原;正常 MVC 專案建置成功且根路由回傳 HTTP 200。執行中的 View 加入未定義符號後,根路由回傳 HTTP 500,伺服器記錄明確顯示 CompilationFailedExceptionCS0103。另一次在建置前保留該錯誤時,固定 SDK 的 build 也以一個 CS0103 失敗,證明簡單 Razor 符號錯誤可在建置閘門提早攔截。這些是功能驗證,不是效能測試,本文不宣稱任何速度或資源改善數字。

遷移決策、風險與交付清單

交付前應以路由矩陣和可回讀證據判斷是否完成;只要關鍵 View 尚未通過,就把狀態維持在遷移中,禁止推論已可正式上線。

首先,Runtime Compilation 應限制在開發或遷移測試用途。正式發佈偏好建置期與發佈期編譯,讓錯誤在交付前暴露,也避免把不必要的編譯元件帶入部署。其次,套件版本要跟目標框架主版本一致,並由 restore graph 決定升級範圍;不要把單一事件中的修正版本當成所有方案的標準答案。

再次,建立路由所有權。每個 View 家族要有對應的匿名測試或受控整合測試,涵蓋 Layout、Partial、錯誤頁與條件分支。測試只使用本機、容器或專用測試資料,所有寫入皆可丟棄。停止測試程序時,要同時回讀程序狀態與端點,避免跨 shell 引號處理造成命令看似成功、程序實際仍在執行。

交付檢查清單:

  • global.json 固定支援中的 .NET 8 SDK,CI 與開發環境使用相同策略。
  • restore 與 build 分開執行,完整記錄保留 NUMSBCS 診斷。
  • 直接與傳遞套件版本已從實際資產圖核對,沒有套件降版。
  • 類別庫、Web 專案與方案三個層級都能獨立判讀退出碼。
  • 首頁、Layout、Partial、錯誤頁與關鍵匿名路由都已送出真實 GET。
  • Runtime Compilation 僅存在於受控測試設定,正式發佈策略另行驗證。
  • 失敗請求保存狀態碼、View 路徑與伺服器診斷,沒有把產生碼座標硬套成原始行號。
  • 沒有使用正式資料、真實帳號、付款流程或不可逆操作做遷移驗證。

核心結論很簡單:ASP.NET Framework 遷移 .NET 8 的驗收要從「能編譯」升級成「每個階段皆可觀察、每條關鍵路由皆可重現」。建置是必要條件,但只有 restore、build、startup 與 request 四道閘門共同通過,才能合理判斷 Razor 遷移範圍已完成。

廣告