從 .NET Framework 遷移到 .NET 的著手前檢核表

· 更新日期: · · .NET, .NET Framework, C#, 現代化, Windows 開發, 遷移

更新紀錄(2 筆,最後更新 2026年09月04日)

本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。

已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22279400)
補上了日文原文中已有的諮詢引導(consultation_services)。內文沒有改動。 查看更新前的版本 (DOI: 10.5281/zenodo.21616286)
初次發布
引用本文(DOI: 10.5281/zenodo.21616285)

本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。

Go Komura(2026)。〈從 .NET Framework 遷移到 .NET 的著手前檢核表〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616285 https://comcomponent.com/zh-TW/blog/2026/03/15/003-dotnet-framework-to-dotnet-premigration-checklist/

DOI(最新版本)
10.5281/zenodo.21616285
DOI(此版本)
10.5281/zenodo.22297125

下載附中日工作表的 Excel 檢核表

這個檔案由 Checklist-ja 與 Checklist-en 兩張工作表組成,把第 13 章的著手前檢核表拆成 方針 / 現行 .NET Framework 側的整備 / 應用程式種類與技術選型 / 不支援的技術與要注意的 API / Windows 專用相依 / shared library 與資料存取 / 維運與 build 這 7 個分類共 34 個項目。Status 與 Notes 兩欄留空,可以直接當成各專案的盤點表使用。讀文章之前先瀏覽一遍,應該會比較容易掌握到第 13 章為止的脈絡。

把 .csproj 的 TargetFramework 改成 net10.0,更新幾個 NuGet 套件,建置通過就結束。

……如果遷移是這樣,那日子相當太平。實際上多半不會這麼順。

.NET Framework 的實務現場裡,System.Web、WCF、Web Forms、舊的 packages.config、web.config.install.xdt、原生 DLL、COM / ActiveX、只在設計階段才能動的第三方控制項、隱含的 x86 前提、依賴設計工具的 ResX、老舊的序列化器等等,平常不會意識到的前提還沉睡著好幾個。

所以,從 .NET Framework 遷移到 .NET 真正重要的,是進入實作之前的盤點。 只要在著手前就把論點拆開,遷移就不是「一場大賭注」,而是「依序逐一排除的工作」。

盤點會改變遷移的性質說明只要在進入實作前先盤點並把論點拆開,遷移就不是大賭注而是依序逐一排除的工作的圖。跳過盤點就會沉睡著的隱含前提著手前的盤點把論點拆開變成依序逐一排除的工作遷移變成一場大賭注

圖 1: 能不能在著手前把論點拆開,決定了遷移是賭注還是工作。

本文整理的是:把既有的 .NET Framework 4.x 業務應用程式 遷移到 目前的 .NET 之前,應該先確認哪些事。主要對象大致是這些。

  • 類別庫
  • 主控台應用程式
  • Windows 服務
  • WinForms / WPF
  • ASP.NET Framework(MVC / Web API / Web Forms)
  • 使用 WCF 的應用程式
  • 使用 EF6 的應用程式

撰寫時間點是 2026-03-15。支援期間與官方工具的建議都會變動,如果閱讀的時間距離較遠,請一併確認官方資訊。

1. 先講結論

先把不太會偏掉的結論放在前面。

  • 遷移前先整理 .NET Framework 側 是第一步。Microsoft 的官方指南也建議在移植前先升級到 .NET Framework 4.7.2 以上,並先完成 PackageReference 化、SDK 樣式化與相依性更新。
  • 難度由 應用程式模型(app model)而不是程式碼量 決定。類別庫與主控台相對輕,ASP.NET Framework、Web Forms、WCF 伺服器、WF(Workflow Foundation) 則容易變重。
  • WinForms / WPF 即使遷移到 .NET,仍然是 Windows 專用。這裡誤解了,就會踩到「遷移完了卻裝不進 Linux 容器」這個經典地雷。
  • ASP.NET Framework → ASP.NET Core 實質上是架構遷移。小型應用程式有時可以一口氣做完,但大型正式系統以分段遷移為前提比較安全。
  • WCF 與 EF6 有可能和 runtime 的遷移切開處理。 WCF 用戶端有給 .NET 用的受支援套件,而 EF6 可以在搬到 modern .NET 之後,再分離出來遷移到 EF Core。
  • 另一方面,建立 AppDomain、.NET Remoting、CAS(Code Access Security,程式碼存取安全性)、COM+、Workflow Foundation、BinaryFormatter 相依 都是紅燈。不先找出來,後面的工時就會爆掉。
  • packages.config / install.ps1 / XDT(XML Document Transform,用來轉換 web.config 等檔案的機制)/ content 資產 / 原生 DLL / COM / ActiveX / x86 前提,即使建置能過,執行階段或設計階段仍容易當掉,要在著手前清查出來。
  • 2026-03 這個時間點,.NET 10 是 LTS。新的遷移要著地在哪裡,基本上以 現行 LTS 為基準思考最自然。
  • 沒有準備測試、量測與回復手段的遷移很危險。與其說遷移是實作工作,不如說是把前提條件一個一個攤開來看清楚的工作。

圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 30 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle

2. 先決定「現在是不是真的該遷移」

最先要決定的不是「要怎麼遷移」,而是 這個應用程式現在真的該遷移嗎。

這裡含糊帶過,往往會做出技術上正確、但對事業來說太沉重的遷移;或者反過來,明明該遷移卻一直往後拖的判斷。

最先要決定的問題說明在決定怎麼遷移之前應先決定這個應用程式現在是不是真的該遷移,這裡含糊就容易做出對事業太沉重的遷移或拖延過久的圖。先決定這個含糊地往前走現在真的該遷移嗎要怎麼遷移太沉重的遷移 或 拖延過久

圖 2: 不先決定「現在該不該遷移」而先問「怎麼遷移」,判斷就會往其中一邊偏掉。

2.1 判斷留在 .NET Framework,也很正常

只要 .NET Framework 4.8.1 跑在受支援的 Windows 上,就會持續獲得支援。也就是說,這件事並不是 不立刻把全部搬到 modern .NET 就馬上有危險 這麼單純。

不過,留下來有明確的限制。

  • 走不出 Windows 專用
  • 得繼續背著 ASP.NET Web Forms 與舊的伺服器技術堆疊
  • 較難享受到新版 .NET 的效能改善、語言功能與生態系
  • 容易和雲端、容器、CI/CD 的當代前提脫節

反過來說,如果這類相依很深,暫時收攏到 .NET Framework 4.8.1 穩定維運,同時另外開一條線擬定替換計畫,是合理的做法。

留下來這個合理的選擇說明如果對 Web Forms、WCF 伺服器相容性或 COM+ 等舊有技術的依賴很強,暫時收攏到 .NET Framework 4.8.1 穩定維運並另開一條線擬定替換計畫是合理做法的圖。有很強的舊有技術相依暫時收攏到 4.8.1 穩定維運另開一條線做替換計畫Windows 專用等限制仍然存在

圖 3: 相依很深時,用 4.8.1 穩定維運加上替換計畫的雙軌做法比較務實。

  • Web Forms 的畫面資產非常多
  • 必須嚴格維持 WCF 伺服器的相容性
  • 對 Workflow Foundation 或 COM+ 的依賴很深
  • 第三方的設計階段元件不支援 modern .NET
  • 商業上不允許做大的規格變更

2.2 每個選項各自會改變什麼

選項 會變好的地方 會留下 / 會失去的地方 適合的情況
留在 .NET Framework 4.8.1 不動既有資產,容易穩定維運 Windows 專用、舊的應用程式模型、現代化的天花板 有很強的舊有技術相依,現階段以事業為優先想穩定維運
遷移到 modern .NET 但留在 Windows 可以把執行階段與工具鏈現代化。效能、開發體驗、SDK 樣式的好處都很大 Windows API 相依仍然存在。不會變成跨平台 WinForms / WPF、Windows 服務、會用到 Windows API 的業務應用程式
遷移到 modern .NET,並把將來的 Linux / 容器 / 雲端也納入視野 部署目標的自由度變高。維運模式也比較好翻新 必須先把 Windows 專用 API 與 app model 剝掉 想把伺服器端往雲端靠,也想順便翻新基礎架構

重要的不是 想不想遷移,而是先決定 遷移之後想著地在哪裡。

要決定的是著地點說明重要的不是想不想遷移,而是先決定遷移之後想著地在哪裡的圖。作為問題並不足夠決定這個想不想遷移要先決定的事遷移之後想著地在哪裡選項與該看的論點就定下來了

圖 4: 把問題從「想不想遷移」換成「想著地在哪裡」,選項就定下來了。

3. 該先決定的 4 個方針

3.1 著地點的 .NET 版本

在撰寫時間點,依 Microsoft 的支援政策,.NET 10 是 LTS。 另一方面,.NET 8 LTS 與 .NET 9 STS 都預計在 2026-11-10 結束支援。 明明是 STS 卻和 LTS 同一個日期,是因為 STS 的支援期間從 18 個月延長到 24 個月。若用「STS 是 18 個月」這個舊前提去算,.NET 9 看起來會是 2026-05-12 結束,所以要拿日期當根據時,請用官方的生命週期資訊確認。

因此,今後要從 .NET Framework 遷移過來,除非有特別的理由,否則 以現行 LTS 作為著地點 最自然。

這裡的實務想法很單純。

  • 想短時間結束的小型遷移,就直接著地在現行 LTS
  • 以長期維運為前提的核心系統,同樣以現行 LTS 為基本
  • 「因為既有函式庫的緣故想用前一個 LTS」是可能存在的理由,但要用日期查看 會維護到什麼時候 再判斷
著地版本的思考方式說明小型遷移與核心系統都以現行 LTS 為基本,若因既有函式庫的緣故想用前一個 LTS,則要用日期查看維護期限再判斷的圖。基本既有函式庫的緣故選擇著地點的 .NET著地在現行 LTS也考慮前一個 LTS用日期確認維護期限後判斷

圖 5: 著地點以現行 LTS 為基本,要選舊 LTS 就把維護期限的日期當根據。

3.2 是要維持 Windows 專用,還是要瞄準將來跨平台

這個判斷會大幅改變該看的論點。

  • 維持 Windows 專用 的話,可以走務實路線:用 WPF / WinForms 與 Windows Compatibility Pack,先把執行階段現代化。
  • 將來也想走向 Linux 或容器化 的話,就必須及早盤點 System.Drawing.Common、登錄檔、WMI、EventLog、Windows 服務、COM、Office Interop 這類以 Windows 為前提的 API。

不決定這件事就開始遷移,中途就會出現「固定在 Windows 真的可以嗎」「不對,我們本來是想放進容器的」這種前後矛盾。

Windows 專用或跨平台的分岔說明維持 Windows 專用可以用相容套件先把執行階段現代化走務實路線,而將來想走向 Linux 或容器化則必須及早盤點以 Windows 為前提的 API 的分岔圖。維持 Windows 專用將來也要上容器不決定就開始要瞄準哪一邊用相容套件把執行階段現代化及早盤點 Windows 前提的 API中途話會前後矛盾

圖 6: 不先決定這個分岔,該看的論點就定不下來,中途會自相矛盾。

3.3 是要一口氣做完,還是分段遷移

遷移的型態大致有 3 種。

  • 接近 in-place 的一次性遷移
  • side-by-side 讓新舊並存的遷移
  • 以 route / library 為單位一點一點收攏的分段遷移

尤其在 ASP.NET Framework 應用程式上,Microsoft 的指南也明確介紹了 incremental migration。 如果條件是不想停掉正式環境、功能數量多、周邊相依多,那麼從一開始就以分段遷移為前提來設計比較不勉強。

遷移的 3 種型態說明遷移有接近 in-place 的一次性遷移、side-by-side 讓新舊並存的遷移,以及以 route 或 library 為單位一點一點收攏的分段遷移這 3 種型態,而不想停掉正式環境時以分段遷移為前提比較不勉強的圖。選擇遷移的型態一次性遷移(偏 in-place)side-by-side 新舊並行route / library 為單位的分段遷移不想停掉正式環境就以這個為前提

圖 7: 型態有 3 種,功能多又停不下來的正式系統就把分段遷移當前提。

3.4 這次要把什麼「排除在遷移範圍之外」

遷移之所以容易失敗,是因為想做的事塞太多。

舉例來說,同時做下面這些就容易變重。

  • .NET Framework → .NET
  • ASP.NET Framework → ASP.NET Core
  • EF6 → EF Core
  • Windows 伺服器 → Linux 容器
  • 變更驗證基礎架構
  • 變更日誌 / 監控基礎架構
  • 資料庫的遷移

當然,全部可能總有一天都需要。 但 是不是非得同時做 是另一回事。

實務上,像下面這樣分開比較順利。

  1. 先把 runtime 與專案結構現代化
  2. 在此之上搬移 app model
  3. 最後才更新 ORM、驗證、雲端、監控
不塞太多的分離順序說明先把 runtime 與專案結構現代化,在此之上搬移 app model,最後才更新 ORM 與驗證、雲端、監控這個分離順序的圖。同時塞太多就會把 runtime 與結構現代化搬移 app model更新 ORM、驗證、雲端、監控遷移變重而容易失敗

圖 8: 不要全部同時做,照 runtime、app model、周邊的順序分開就不會太重。

4. 著手前要先整好的基礎

Microsoft 的移植前指南相當務實。 簡單說,就是 在遷移前先把現在的 .NET Framework 專案收攏到現代化的入口 這件事。

4.1 升級到 .NET Framework 4.7.2 以上

官方指南建議在移植前把目標設定為 .NET Framework 4.7.2 以上。 理由是即使 .NET Standard 沒有原封不動地保留既有 API,也比較容易改用較新的 API 替代方案。

實務上,可以的話以 4.8.1 為基準思考會比較清楚。

  • 從支援的角度來看很直接了當
  • 容易當成 .NET Framework 側的最終穩定點來處理
  • 比較容易立起「先在現行 Framework 側整理」這個方針

先做這件事會改變什麼

  • .NET Standard 2.0 共用函式庫的處理比較容易穩定
  • 可以先減少來自舊執行階段的雜訊
  • 比較容易分辨相容性問題的原因是「Framework 太舊」還是「轉成 modern .NET」

4.2 統一改用 PackageReference

移植前指南建議把參考收攏成 PackageReference 格式。 先做這件事,相依性管理的全貌會清楚很多。

改成 PackageReference 會改變什麼

  • 套件參考集中到 csproj
  • 遞移相依性變得容易看清
  • restore 的前提會和 modern .NET 側對齊
  • 和 CLI / CI 的搭配變好

不過,這裡有地雷。

典型的地雷

NuGet 的官方文件明確寫出了從 packages.config 遷移到 PackageReference 的下列限制。

  • Visual Studio 的內建遷移功能在 ASP.NET 專案上不能用
  • 依賴 install.ps1 / uninstall.ps1 的套件,有時不會照預期運作
  • content 資料夾的資產有時會被忽略
  • web.config.install.xdt 這類 XDT 轉換不會被套用
  • lib 直下組件結構過舊的套件,有時無法順利解析

也就是說,最好不要以為這 只是換一下套件格式。 尤其 classic ASP.NET 相當盛行在安裝 NuGet 套件時改寫 web.config 的做法,所以遷移時隱含的前提特別容易浮上檯面。

PackageReference 化會讓隱含前提浮上檯面說明從 packages.config 遷移到 PackageReference 時,install.ps1、XDT 轉換與 content 資料夾的資產有時不會被套用,因此安裝時改寫 web.config 的隱含前提容易浮上檯面的圖。轉換成 PackageReferenceinstall.ps1 有時不會運作XDT 轉換不會被套用content 資產有時會被忽略隱含的前提浮上檯面

圖 9: 就算只想做格式轉換,過去仰賴安裝時機關的前提也會一口氣浮上檯面。

要用什麼來轉換

不是只有手工一途。不過每種道具的守備範圍都不一樣。

道具 能做到什麼 前提與注意事項
Visual Studio 的轉換功能 在方案總管中對「參考」或 packages.config 按右鍵,選 Migrate packages.config to PackageReference... 就會轉換 需要 Visual Studio 2017 15.7 以上。在 ASP.NET 與 C++ 的專案上不能用。選單沒出現時,先跑一次 NuGet 還原或開一次套件管理員,再重新按右鍵
.NET Upgrade Assistant 把專案的分析與升級一併處理 官方文件上已經 不建議使用,並引導改用 GitHub Copilot 現代化
GitHub Copilot 現代化 支援評估、規劃、程式碼修改到驗證。是目前官方引導的重心 如 4.5 所述,前提是 Visual Studio 與 Copilot,以及 C# 程式碼
手工做 SDK 樣式化 新建一個 csproj,只把必要的項目搬過去 檔案數量少的專案,有時這樣反而最快

Visual Studio 的轉換功能會在執行前先為專案建立備份,最後輸出轉換報告(頂層相依、遞移相依、偵測到的相容性問題)。想還原時,文件寫的步驟是:從備份資料夾把 csproj 與 packages.config 寫回去,再到套件管理員主控台執行 update-package -reinstall。

也就是說,可以用能還原的形式先試試看。在著手前的估算階段,先只轉換 1 個專案並讀報告是最快的做法。

用能還原的形式試轉換說明 Visual Studio 的轉換功能會先建立備份再轉換並輸出轉換報告,因此可以先只轉換 1 個專案讀報告,必要時再從備份寫回去的圖。有問題就沒問題就會建立備份只轉換 1 個專案讀轉換報告寫回去並重新安裝擴大到其他專案

圖 10: 轉換附帶備份與報告可以先試,所以先用 1 件來鎖定可疑範圍。

4.3 收攏成 SDK 樣式

移植前指南也建議轉換成 SDK 樣式的專案格式。

這一步相當管用。

改成 SDK 樣式會改變什麼

  • csproj 大幅變簡潔
  • 和 PackageReference 的搭配很好
  • 比較容易做 multi-targeting
  • 容易收攏成以 dotnet build / dotnet test / dotnet publish 為中心的 CI/CD
  • 因為接近 modern .NET 側的結構,後段的差異會變小

反過來說,維持舊 csproj 與舊 NuGet 管理就直接跳到 modern .NET,差異會太大。

用 SDK 樣式化縮小差異說明維持舊 csproj 與舊 NuGet 管理直接跳到 modern .NET 差異會太大,因此先收攏成 SDK 樣式接近 modern .NET 側的結構,就能縮小後段差異的圖。直接跳過去先做 SDK 樣式化舊 csproj + 舊 NuGet 管理差異太大接近 modern 側的結構後段的差異變小

圖 11: 中間夾一段 SDK 樣式化,跳到 modern .NET 時的差異就會小一級。

4.4 先更新相依性

這也和官方指南一致:把相依性收攏到 可用的最新版本,可以的話收攏到 支援 .NET Standard 的版本。

先做這件事的意義

  • 可以早一點知道「這個套件在 modern .NET 上到底能不能用」
  • 可以避免舊相依性變成雜訊
  • 比較容易把 shared library 改成 netstandard2.0
  • 後段的遷移工作比較容易集中在「程式碼的移植」上
先更新相依性的意義說明先把相依性更新到較新的版本,就能早一點知道能不能在 modern .NET 上使用,避免舊相依性造成的雜訊,並讓後段的遷移工作集中在程式碼移植上的圖。先把相依性更新到最新能早一點知道是否支援 modern .NET能減少舊相依性的雜訊後段可以集中在程式碼的移植

圖 12: 相依性的更新越早做完,遷移本體的工作就越能集中在移植本身。

4.5 也先確認官方工具的前提

2026-03 這個時間點,Microsoft 引導的重心已經移到 GitHub Copilot 現代化 這一側。 與其只把傳統的遷移輔助工具當前提,不如把它看成涵蓋評估、規劃、程式碼修改到驗證的一整套支援流程,比較符合實務。

不過,現行文件的前提是 Visual Studio 2026 或仍在支援中的 Visual Studio 2022 系列、GitHub Copilot,以及 C# 程式碼。

為什麼需要做這個確認

  • 對官方工具能期待什麼,會因此改變
  • 可以把團隊的 IDE / build agent / 擴充功能的前提對齊
  • 可以做出 VB.NET 方案不要對自動化期待過高 這個判斷

混著 VB.NET 的實務現場並不少見。 所以「最新的官方工具能幫到什麼程度」,值得在最開始就確認清楚。

官方工具的現況與前提說明 .NET Upgrade Assistant 已不建議使用,引導重心移到 GitHub Copilot 現代化,但前提是 Visual Studio、Copilot 與 C# 程式碼,因此 VB.NET 方案需要做出不要對自動化期待過高的判斷的圖。不建議使用、引導去向如果混著 VB.NET.NET Upgrade AssistantGitHub Copilot 現代化前提: Visual Studio + Copilot + C#不要對自動化期待過高

圖 13: 工具的重心已移到 Copilot 現代化,不符合其前提的現場就得另外想辦法。

5. 估算各種專案類型的難度

遷移常被籠統地講成「從 .NET Framework 到 .NET」,但實際上 每種專案類型都是另一場遊戲。

5.1 大致的難度感

類型 難度感 主要論點
類別庫 低〜中 API 相容性、相依性、目標框架的切分
主控台 / 批次 / 部分 Windows 服務 低〜中 散發方式、原生相依、設定
WinForms / WPF 中 仍是 Windows 專用、設計工具、第三方 UI、BinaryFormatter 周邊
ASP.NET MVC / Web API 中〜高 往 ASP.NET Core 的 app model 遷移、驗證、工作階段、組態、DI
ASP.NET Web Forms 高 畫面模型差異很大、以置換 UI 層為前提
WCF 用戶端 中 套件置換、合約、組態
WCF 伺服器 高 選 CoreWCF 還是改設計成 gRPC / HTTP API
EF6 → EF Core 同時進行 高 ORM 是另一套東西、行為差異、遷移歷程

5.2 類別庫的關鍵在於「共用邊界」怎麼切

類別庫相對好搬。 不過,那只限於 函式庫真的像函式庫那樣被分離出來 的時候。

有下面這類相依時,難度就會上升。

  • 碰到了 System.Web
  • 直接看 HttpContext.Current
  • 公開 API 裡含有 WPF / WinForms 的型別
  • 太靠近登錄檔、WMI、EventLog 這類 Windows API
  • 依賴 AppDomain 或 Remoting

用 只切得出業務邏輯就輕,連 app model 都抱在手上就重 來看會比較清楚。

類別庫輕重的判讀方式說明只切得出業務邏輯的類別庫較輕,而抱著 System.Web、UI 型別、Windows API、AppDomain 等 app model 的類別庫會變重的圖。只切得出邏輯連 app model 都抱著檢視類別庫遷移很輕遷移很重System.Web、UI 型別、Windows API 相依等

圖 14: 函式庫的難度不是看行數,而是看它抱了多少 app model。

5.3 WinForms / WPF 好遷移,但仍是 Windows 專用

WinForms 與 WPF 可以遷移到 .NET。 不過兩者都 仍然是 Windows 專用框架。

在這裡把期待值搞錯很危險。

  • 會變好的地方
    • 能搭上 modern .NET 的執行階段、語言與 SDK 樣式
    • 容易把 CI/CD 與套件管理收攏成當代做法
    • 比較容易取得部分效能與可維護性的改善
  • 不會改變的地方
    • 仍然是 Windows 專用
    • UI 控制項與設計階段元件的相容問題還在
    • ActiveX / COM / 原生 DLL 的問題不會消失

另外,有些情況 WinForms / WPF 需要確認 BinaryFormatter 的影響。 尤其當剪貼簿、拖放、ResX、設計階段的序列化牽扯到自訂型別時,把目標提升到 .NET 9 以上就特別容易浮上檯面。

WinForms / WPF 遷移的期待值說明 WinForms 與 WPF 可以遷移到 .NET 並搭上 modern .NET 的執行階段、語言與 SDK 樣式,但仍是 Windows 專用這點不會改變,有時還需要確認 BinaryFormatter 的影響的圖。遷移 WinForms / WPF能搭上 modern .NET 的好處仍是 Windows 專用需要確認 BinaryFormatter

圖 15: 桌面 UI 可以遷移,但 Windows 專用這個性質會原封不動地帶過去。

5.4 ASP.NET Framework 不是「runtime 遷移」而是「app model 遷移」

從 ASP.NET Framework 遷移到 ASP.NET Core,在 Microsoft 的指南裡也明講是 non-trivial。 這不只是因為 API 名稱改變,而是因為 作為前提的架構不一樣。

容易出現差異的地方大致是這些。

  • Hosting model
  • Middleware pipeline
  • Request processing model
  • 工作階段 / 快取
  • 驗證 / 授權
  • 組態
  • 相依性注入
  • 日誌 / 監控

ASP.NET Framework 的應用程式要先確認的大致是這些。

  • 可以從哪個 route / endpoint 先搬
  • 能不能把 System.Web 相依從 shared library 剝掉
  • 驗證 / 工作階段 / 例外處理 / 日誌要怎麼對齊
  • 要不要在不停掉正式環境的前提下分段遷移

尤其大型應用程式,從一開始就以 incremental migration 為前提思考比較實際。

ASP.NET 的遷移是 app model 遷移說明從 ASP.NET Framework 到 ASP.NET Core 的遷移不是替換 API 名稱,而是 hosting、middleware、驗證、組態、DI 等前提架構都會改變的 app model 遷移,大型應用程式以分段遷移為前提比較實際的圖。大型應用程式ASP.NET Framework → Core前提架構會改變hosting、middleware、驗證、組態、DI以 incremental migration 為前提規劃

圖 16: ASP.NET 的遷移不是 runtime 而是 app model 的遷移,規模越大越要以分段為前提。

5.5 Web Forms 要從「職責分解」而不是「資產遷移」開始

Web Forms 和 ASP.NET Core 不是同一種 app model。 所以在估算上,不要以畫面資產可以原封不動帶走為前提 比較安全。

實務上,多半會先從下面這些分解開始。

  • 把畫面邏輯與業務邏輯分開
  • 拆解埋在 Page / UserControl / ViewState 裡的職責
  • 把業務邏輯與資料存取抽離到 shared library
  • UI 用 Razor Pages / MVC / Blazor 等另一種模型重新組成

也就是說,Web Forms 的案子重點在於 有沒有比 runtime 的遷移更早準備好職責分解計畫。

Web Forms 從職責分解開始說明 Web Forms 與 ASP.NET Core 不是同一種 app model,因此要從把畫面邏輯與業務邏輯分開、把邏輯抽離到 shared library、UI 以另一種模型重新組成的職責分解開始的圖。以能原封不動帶走為前提Web Forms 的畫面資產把畫面邏輯與業務邏輯分開把邏輯抽離到 shared libraryUI 用另一種模型重新組成估算會垮掉

圖 17: Web Forms 不是資產遷移,要以先拆解職責再用另一種模型重組為前提來看。

5.6 WCF 用戶端與 WCF 伺服器要分開思考

這裡最好不要混為一談。

WCF 用戶端

WCF Client 有給 modern .NET 用的 受支援 NuGet 套件。 所以,如果只是呼叫 WCF 的那一側,有時沒有看起來那麼重。

WCF 伺服器

另一方面,主控 WCF 服務 的那一側就不一樣了。 Microsoft 的指南介紹的現代化路徑大致有下面 2 條。

  • 用 CoreWCF 維持既有用戶端相容性的方向
  • 收攏到 gRPC 等現代 RPC / HTTP 基礎的方向

不過 CoreWCF 並不是把 WCF 的全部原封不動搬過來,而是子集合。 它適合維持與既有用戶端的相容性,但 程式碼修改與測試是前提。

WCF 要分用戶端與伺服器分別評估說明 WCF 用戶端有給 modern .NET 的受支援套件因此有時沒看起來那麼重,而伺服器側必須選擇維持相容的 CoreWCF 或改設計成 gRPC 等的圖。用戶端伺服器維持相容重新設計WCF 用在哪一側用受支援的套件遷移選擇路徑CoreWCF(子集合)gRPC / HTTP API

圖 18: 呼叫端與主控端的難度和選項都不同,不要混在一起估算。

6. 清查在 .NET 上不能用 / 直接搬容易卡住的技術

這是著手前絕對要做的一段。 Microsoft 有一份 在 .NET Framework 上能用、但在 .NET 6+ 上不能用的技術 清單。

6.1 容易變成紅燈的技術

技術 在 .NET 上的狀態 該怎麼看待
AppDomain.CreateDomain 等建立 AppDomain 的用法 不支援 隔離改用其他處理程序 / 容器 / AssemblyLoadContext 來思考
.NET Remoting 不支援 改設計成 IPC、HTTP、gRPC、Socket、管道等
CAS / Security Transparency 作為安全性邊界不支援 改用 OS / 容器 / 權限分離來思考
System.EnterpriseServices(COM+) 不支援 把以 COM+ 為前提的設計分離、置換掉
Workflow Foundation 不支援 連 CoreWF 等替代方案一起,另外估算
WCF server 內建的情況下無法直接運作 選 CoreWCF 還是 gRPC
BinaryFormatter .NET 9 以後實作一律拋出例外 遷移序列化器,稽核 ResX / 剪貼簿 / 拖放

6.2 AppDomain 是「即使部分 API 還在,建立仍是另一回事」

AppDomain 周邊稍微麻煩一點。 即使在 .NET 上仍留有部分 API surface,但 建立新的 AppDomain 來隔離 這種用法並不受支援。

因此,如果原本是用 AppDomain 做下面這些用途,就需要重新設計。

  • 外掛的隔離
  • 卸除動態載入的內容
  • 部分信任程式碼的隔離
  • 暫時性執行環境的分離

遷移前該看的,不只是 有沒有出現 AppDomain 這個字,而是 當初為了什麼而使用 AppDomain。

AppDomain 的用法會改變判斷說明 AppDomain 即使部分 API 還在,建立新的 AppDomain 來隔離的用法仍不受支援,因此要分辨是只用到型別還是為了隔離,若為隔離則改用其他處理程序或 AssemblyLoadContext 重新設計的圖。只用到型別或資訊參考建立來做隔離出現了 AppDomain多半可以直接搬需要重新設計另一個處理程序 / 容器 / AssemblyLoadContext

圖 19: 不是看字眼有沒有出現,而是看「當初為了什麼而使用」來分辨要不要重新設計。

6.3 Remoting「比看起來更深」

Remoting 本身自不待言,連 委派的 BeginInvoke() / EndInvoke() 呼叫 這類非同步委派也可能落在影響範圍內。這雖然不是 Remoting 本身,但在 modern .NET 上並不受支援,所以要在遷移前清查出來。

因此,搜尋時把下面這些也一起看過,會比較安全。

  • System.Runtime.Remoting
  • MarshalByRefObject
  • RealProxy
  • BeginInvoke( / EndInvoke(

6.4 BinaryFormatter 會因為 target 版本而突然浮上檯面

BinaryFormatter 在越舊的程式碼庫裡,越可能「沒有自覺地在用」。

  • 持久化資料
  • 快取
  • 工作階段的保存
  • 外掛的狀態
  • 剪貼簿 / 拖放
  • ResX
  • WinForms / WPF 設計工具周邊

從 .NET 9 起,BinaryFormatter 的實作不再包含在執行階段中,API 一律拋出 PlatformNotSupportedException。 也就是說,這不是「之後再想」的事,而是 決定 target 版本的那一刻就要先稽核的論點。

BinaryFormatter 浮上檯面的路徑說明 BinaryFormatter 常在持久化資料或 ResX、剪貼簿中被沒有自覺地使用,而 .NET 9 以後 API 一律拋出例外,因此是決定 target 版本時就要先稽核的論點的圖。沒有自覺的使用(ResX、剪貼簿等)把 target 定為 .NET 9 以上API 一律拋出例外決定的當下就先稽核

圖 20: BinaryFormatter 在決定 target 的瞬間就會發作,所以不要往後拖,先稽核。

6.5 先 grep 一遍的搜尋字串

在著手前,光是拿下面這些字串對整個方案搜尋一遍,看到的景色就會很不一樣。

System.Web
HttpContext.Current
System.Runtime.Remoting
MarshalByRefObject
AppDomain
BinaryFormatter
ServiceHost
ChannelFactory
System.EnterpriseServices
Workflow
packages.config
web.config.install.xdt
install.ps1
DllImport
AxInterop
Microsoft.Office.Interop

這些只要找到一個就出局,並不是這個意思。 它是 用來知道哪些地方走標準路線就能遷移、哪些地方要另外拉一條軌道的地圖。

實際的搜尋指令

道具用 rg(ripgrep)或 PowerShell 都可以。

# PowerShell。對方案底下一次搜尋
Get-ChildItem -Recurse -Include *.cs,*.vb,*.config,*.csproj,*.vbproj |
    Select-String -Pattern 'BinaryFormatter|System\.Runtime\.Remoting|MarshalByRefObject|AppDomain\.CreateDomain' |
    Select-Object Path, LineNumber, Line
# ripgrep。想先用件數鎖定可疑範圍時
rg -n --stats "BinaryFormatter|System\.Runtime\.Remoting|AppDomain\.CreateDomain" --glob "*.cs" --glob "*.vb"

找到之後接下來要做什麼

搜尋只是入口,之後的判斷才是正戲。每個字串接下來該看哪裡,都是固定的。

找到的字串 接下來要確認的事 收斂的方向
System.Web / HttpContext.Current 能不能從 shared library 移到外面。呼叫端能不能改裝成 DTO 傳遞 用 8.5 的策略剝掉
System.Runtime.Remoting / MarshalByRefObject / BeginInvoke 當初的遠端呼叫是為了什麼。是處理程序之間、機器之間,還是單純的非同步呼叫 改設計成 IPC、HTTP、gRPC 或以 Task 為基礎
AppDomain 只是用到型別名稱,還是用 CreateDomain 做隔離 若目的是隔離就改用其他處理程序或 AssemblyLoadContext(6.2)
BinaryFormatter 寫出去的資料之後還需不需要讀回來。有沒有透過 ResX 或剪貼簿間接使用 遷移到別的序列化器,並為既有資料擬定改讀計畫(6.4)
ServiceHost / ChannelFactory 是主控端還是用戶端 照 5.6 分開評估
packages.config / install.ps1 / web.config.install.xdt 安裝時到底改寫了什麼 4.2 的地雷。把設定明確地重新掌握在自己手上
DllImport / AxInterop / Microsoft.Office.Interop 位元數的前提,以及執行階段需要的整套檔案 前往 9.4 的位元數確認

做到這裡,得到的就不是「能不能遷移」,而是 「哪些走標準路線、哪些要另外估算」 的清單。著手前想要的,正是這種形式的清單。

從搜尋到清單說明搜尋只是入口,順著每個找到的字串往下確認,就會變成哪些能走標準路線遷移、哪些要另外估算的清單的圖。用搜尋字串清查方案逐字串往下確認該看的地方變成標準路線與另外估算的清單找到不等於馬上出局

圖 21: 搜尋本身只是入口,往前推進到判斷,才會變成著手前想要的那種清單。

7. 決定要容忍多少 Windows 專用前提

遷移時常見的誤解是「搬到 .NET 就會變成跨平台」。 沒有這種魔法。應用程式如果和 Windows 綁得很深,遷移之後照樣是 Windows 專用。

7.1 維持 Windows 專用搬過去,是很務實的

Microsoft 有 Windows Compatibility Pack,可以讓登錄檔、WMI、EventLog、Windows 服務、Directory Services 等許多 Windows 系 API 在 modern .NET 上使用。

它的存在在實務上很重要。

  • 先想搬到 modern .NET
  • 但 短期內不會離開 Windows
  • 所以 Windows API 相依先暫時容忍

在這樣的現場,它是很有力的選項。

遷移的第一個目標,不一定非得是 跨平台化。

7.2 不過 Windows 專用 API 也是「之後才會顯現的債」

有 Windows Compatibility Pack 並不代表什麼都安全。

  • 想裝進 Linux 容器
  • 想以 Kubernetes 為前提運轉
  • 想讓 macOS / Linux 的開發者也跑同一套 build
  • 想在將來的雲端減少 Windows VM

如果有這些目標,Windows API 相依最好趁現在攤開來看清楚。

相容套件的用武之地與債務說明短期內不離開 Windows 時可以用 Windows 相容套件暫時容忍 Windows API 相依而搬到 modern .NET,但若將來要走向容器或雲端,那些相依會變成之後才顯現的債而應趁現在攤開來看清楚的圖。短期內維持 Windows若將來要走向容器與雲端先想搬到 modern .NET用相容套件暫時容忍相依相依會變成之後才顯現的債趁現在先攤開來看清楚

圖 22: 相容套件是務實的橋,但那些相依會不會變成債,取決於目標,所以先攤開來看。

7.3 System.Drawing.Common 特別容易被誤解

System.Drawing.Common 在 .NET 6 以後是 Windows 專用函式庫。 如果有程式碼用它做影像處理或文字繪製,就得先決定要走哪一邊。

  • 要繼續在 Windows 上維運嗎
  • 還是將來也想在 Linux / macOS 上跑

前者的話,有些情況暫時維持現狀也可以。 後者的話,就必須從一開始就把換成 SkiaSharp 或 ImageSharp 納入遷移計畫。

System.Drawing.Common 的分岔說明 System.Drawing.Common 在 .NET 6 以後是 Windows 專用函式庫,繼續在 Windows 上維運時有些情況可以暫時維持現狀,若將來也要在 Linux 或 macOS 上跑則必須從一開始就把換成 SkiaSharp 或 ImageSharp 納入計畫的圖。繼續在 Windows 上維運也想在 Linux / macOS 上跑有用到 System.Drawing.Common有些情況暫時維持現狀也可以換成 SkiaSharp / ImageSharp從一開始就納入遷移計畫

圖 23: System.Drawing.Common 是 Windows 專用,所以處理方式從一開始就依著地點而分岔。

7.4 顯示 Windows 綁定的典型味道

出現下面這類參考或 API 時,以「至少一開始維持 Windows 專用搬過去」為前提來估算比較安全。

  • Microsoft.Win32.Registry
  • System.Management
  • System.Diagnostics.EventLog
  • System.ServiceProcess
  • System.DirectoryServices
  • System.Drawing
  • DllImport / P/Invoke
  • COM 參考
  • AxInterop.*
  • Microsoft.Office.Interop.*

8. 共用函式庫的切法會改變難度

在比較大的方案裡,說 遷移的成敗由 shared library 的切法決定 並不誇張。

8.1 先分類

函式庫大致分成 3 種來整理會比較清楚。

  1. 純粹的業務邏輯 / 領域邏輯
  2. 稍微依賴 app model 的中間層
  3. 與 UI / Web / Windows API 緊貼的層

其中最該優先搬的是 1。

  • 計算
  • 規則判定
  • DTO / 合約
  • 領域服務
  • 單純的資料轉換

這一塊如果能乾淨地切出來,難度會一口氣下降。

函式庫的 3 種分類與搬移順序說明把函式庫分成純粹的業務邏輯、稍微依賴 app model 的中間層,以及與 UI 或 Windows API 緊貼的層這 3 種,而最先搬的是純粹的業務邏輯的圖。最先搬其次最後純粹的業務邏輯能乾淨切出來就會降低難度偏 app model 的中間層UI、Windows API 緊貼層

圖 24: 函式庫分成 3 層,從純粹的邏輯開始依序救出來。

8.2 netstandard2.0 到現在仍是有效的橋

Microsoft 的指引裡,如果是 需要和 .NET Framework 側共存的 shared library,基本上會先考慮 .NET Standard 2.0。

這裡重要的有 2 點。

  • .NET Framework 不支援 .NET Standard 2.1
  • 如果想讓 shared library 被 old / new 兩邊參考,2.0 比較容易成為務實解
netstandard2.0 這座橋說明 .NET Framework 不支援 .NET Standard 2.1,因此想讓 shared library 被新舊兩邊參考時,.NET Standard 2.0 比較容易成為務實解的圖。可以參考可以參考Framework 不支援.NET Framework 側netstandard2.0 的 shared librarymodern .NET 側netstandard2.1

圖 25: 作為新舊兩邊都能參考的橋,務實解是 2.0 而不是 2.1。

8.3 改成 netstandard2.0 會改變什麼

方針 會怎麼改變 適合的情況 注意事項
改成 netstandard2.0 old / new 兩邊都容易參考 純粹的業務邏輯、共通合約、工具函式 app model 專屬的 API 放不進去
multi-target(例如 net48;net10.0) 保持共通程式碼的同時,可以保留環境差異 只有些微環境差異的函式庫 條件分支與 build 管理會變多
直接只做 net10.0 將來最乾淨 不需要 old / new 共存的新層 無法從 .NET Framework 參考

8.4 相容模式不是萬能

.NET Standard 2.0 有一個可以參考 .NET Framework 函式庫的相容模式。 不過,這 並不是什麼都能透明運作的魔法。

例如以 WPF 這類 app model 專屬 API 為前提的函式庫,就是很正常地難。 也就是說,即使叫做 shared library,也要限縮到真正能共用的職責上,這一點很重要。

8.5 ASP.NET 系的函式庫,勝負在能不能剝掉 System.Web

要對 ASP.NET Framework 做分段遷移時,如果 shared library 直接掛在 HttpContext.Current 或 System.Web 上,會相當辛苦。

這時的基本策略是下面其中一種。

  • 把 System.Web 相依推到介面之外
  • 改成以 DTO 的形式接收來自 HttpContext 的資訊
  • 在遷移的過渡期使用 adapter
  • 還是不行的話,就用 multi-target 分階段剝掉

8.6 函式庫要用 leaf-first 往上升

ASP.NET 的 incremental migration 指南明確寫著,supporting library 要以 postorder depth-first,也就是 從葉子開始依序 往上升。

這不限於 Web,在一般的方案裡也相當有效。

  • 因為相依對象已經先升好了,上層的全貌比較容易看清
  • 比較容易把相容性問題局限在小範圍
  • 比較容易以函式庫為單位做測試
用 leaf-first 往上升說明把 supporting library 從相依的葉子開始依序往上升,因為相依對象已經先升好了,上層的全貌更容易看清,也比較容易把相容性問題局限在小範圍並以函式庫為單位測試的圖。先升相依葉子端的函式庫升相依對象已完成的中間層最後才是上層的應用程式層可以把問題局限在小範圍測試

圖 26: 從葉子依序往上升,等到動上層時腳下已經都備齊了。

9. 盤點 NuGet / 外部相依 / 第三方元件

這一段做得草率,遷移的後半會吃最大的苦頭。

9.1 相依性分成 4 種來整理會比較清楚

  1. 公開的 NuGet 套件
  2. 公司內部的 private package / internal library
  3. 本機 DLL 參考
  4. COM / ActiveX / 原生 DLL / SDK

其中只看 1 是不夠的。 真正危險的是 3 和 4。

相依的 4 種分類與危險度說明相依性可分成公開 NuGet、公司內部套件、本機 DLL 參考,以及 COM 或 ActiveX 或原生 DLL 這 4 種,只看公開 NuGet 並不夠,真正危險的是本機 DLL 與 COM 及原生那一側的圖。盤點相依性公開 NuGet、公司內部套件本機 DLL 參考COM / ActiveX / 原生 DLL真正危險的是這一側

圖 27: 只數公開 NuGet 並不夠,本機 DLL 與 COM、原生這一類才要先清查。

9.2 每項相依要確認的事

對每一項相依,至少要確認到這個程度。

  • 有沒有以 modern .NET 為目標
  • 是否支援 PackageReference
  • 用 SDK 樣式有沒有問題
  • 有沒有 x86 / x64 / ARM64 的限制
  • 有沒有依賴設計階段工具或 Visual Studio 擴充功能
  • 有沒有以 install script / config transform 為前提
  • 是否還在持續支援

9.3 第三方 UI / 報表 / 設計階段元件要另外估算

在 WinForms / WPF / ASP.NET 的遷移裡,這一塊影響相當大。

  • 表格控制項
  • 報表引擎
  • PDF 輸出元件
  • 圖表元件
  • 與設計工具整合型的 UI 函式庫
  • ActiveX 包裝層

這些不只牽涉執行階段,還牽涉 設計階段的支援。 遷移估算時只看「能不能編譯」,很正常地會估錯。

9.4 原生 DLL 與位元數一定要看

即使在 .NET Framework 時代看起來是用 AnyCPU 在跑,實際上仍可能相依於這些東西。

  • 固定 x86 的 COM
  • 只支援 32 位元的 ActiveX
  • 特定版本的 VC++ Runtime
  • 已簽署的原生 DLL

這一類問題不是轉成 modern .NET 才突然長出來的,而是 本來就存在的限制浮上檯面 而已。 正因如此,在遷移前先攤開來看才有價值。

位元數限制的浮現說明看起來是用 AnyCPU 在跑,實際上仍可能相依於固定 x86 的 COM、只支援 32 位元的 ActiveX 或特定版本的 VC++ Runtime,這些是本來就存在的限制在轉成 modern .NET 時浮上檯面而已的圖。看起來是用 AnyCPU 在跑相依於固定 x86 的 COM、32 位元 ActiveX 等遷移讓本來就有的限制浮上檯面在遷移前先攤開來看清楚

圖 28: 位元數的問題不是遷移造成的,只是讓本來就有的限制變得看得見。

10. 把 EF6、序列化器與資料周邊當成另一個問題處理

runtime 的遷移,和資料存取或序列化的重新設計,盡量當成不同的問題來處理會比較順利。

10.1 EF6 → EF Core 不是直接升級

Microsoft 的 EF 指南也說明,EF Core 是 EF6 的 total rewrite,沒有 direct upgrade path。

因此,在使用 EF6 的應用程式上,下面的順序比較實際。

  1. 先搬到 modern .NET
  2. 必要時維持 EF6 不動,讓應用程式跑起來
  3. 之後再以另一個專案的形式遷移到 EF Core

不要把 runtime migration 和 ORM migration 混在一起。光是這一點,難度就會下降不少。

EF6 與 runtime 分離遷移說明 EF Core 是 EF6 的全面改寫而沒有直接升級路徑,因此先搬到 modern .NET,必要時維持 EF6 不動讓應用程式跑起來,之後再以另一個專案的形式遷移到 EF Core 的順序比較實際的圖。同時做的話先搬到 modern .NET必要時維持 EF6 不動運作之後再以另一個專案遷移到 EF CoreORM 的行為差異混進來而變重

圖 29: runtime 與 ORM 不要同時動,留著 EF6 先搬 runtime 才務實。

10.2 留著 EF6 會改變什麼

  • 好處
    • 可以把資料存取層的差異往後放
    • 可以專心在 business logic 與 app model 的遷移
    • 不會混進「EF Core 的行為差異」
  • 注意事項
    • 就新開發的角度來說,EF Core 才是本命
    • EF6 Designer / EDMX 的使用型態另有限制

10.3 以 EDMX 為基礎的 EF6 要看到「設計階段」

EF6 的文件寫著,EF 設計工具在 .NET / .NET Standard 專案,以及 SDK 樣式的 .NET Framework 專案上不受直接支援。

在以 EDMX 為基礎的應用程式上,必須把這 3 點分開思考。

  • 執行階段能不能運作
  • 設計工具能不能用
  • 產生的程式碼要怎麼處理

如果大量使用 EDMX,這件事最好在一開始就放進估算裡比較安全。

10.4 BinaryFormatter 與自訂序列化容易變成「隱藏相依」

序列化器只靠程式碼搜尋很容易漏看。

  • 持久化格式
  • 訊息傳遞
  • 快取
  • 舊的 WCF / SOAP 合約
  • ResX
  • 剪貼簿 / 拖放

這一帶還牽涉到 資料的相容性。 也就是說,不只是「能不能建置」,還必須確認到 能不能讀取舊資料。

11. 把組態、散發、維運與 CI/CD 都納入遷移對象

遷移的對象不只是原始碼。

11.1 設定檔

在 .NET Framework 側,app.config / web.config 上可能載了相當多東西。

  • 連線字串
  • 自訂的 config section
  • WCF endpoint 設定
  • binding redirect
  • diagnostics
  • ASP.NET 的各種設定
  • 套件安裝時的 transform 結果

在 modern .NET 側,組態的持有方式與讀取路徑有些場合會改變。 所以「設定檔之後再說」是危險的。

最先要做的是 設定的盤點。

  • 設定檔裡有些什麼
  • 哪些在應用程式啟動時是必要的
  • 哪些是環境差異
  • 哪些是由 NuGet 或 installer 自動注入的

11.2 散發方式

散發的形式也要在著手前先看過。

  • 是不是在 IIS 底下
  • 是不是 Windows 服務
  • 是不是 Scheduled Task
  • 是不是 ClickOnce / MSI / 自製 installer
  • 是不是以地端伺服器為前提
  • self-contained / framework-dependent 哪一種比較適合

即使執行本體能遷移,如果 散發與啟動的機制還停留在舊前提,最後還是會卡住。

11.3 日誌、監控與維運程序

維運周邊也很容易漏看。

  • 是不是以 Windows Event Log 為前提
  • 有沒有在看 Performance Counter
  • 是不是以 WMI 為基礎的監控
  • 服務帳戶或權限是不是固定的
  • 日誌的輸出位置是不是以本機檔案為前提

遷移之後程式碼會動,但 維運轉不起來,這種情況很正常地會發生。

11.4 CI/CD 與 build agent

遷移前也要確認下面這些。

  • build agent 上能不能裝必要的 .NET SDK
  • 以 nuget.exe / msbuild.exe 為前提的 pipeline 要怎麼處理
  • 要不要收攏成以 dotnet CLI 為基礎
  • 測試執行、coverage、publish 的工作要怎麼更新
  • 公司內部的範本或 reusable pipeline 是不是以舊格式為前提

在個人本機能動,CI 卻掛掉,是遷移的常見情況。

遷移對象不只是程式碼說明即使執行本體能遷移,設定檔、散發方式、日誌與監控等維運,以及 CI/CD 與 build agent 若還停留在舊前提最後仍會卡住,因此這些也要納入遷移對象的圖。原始碼的遷移光這樣還不會結束設定檔與散發方式日誌、監控、維運程序CI/CD 與 build agent本機能動 CI 卻掛掉

圖 30: 把設定、散發、維運、CI/CD 都算進去,才算真的數完了遷移的對象。

12. 務實的遷移推進方式

把前面的論點放在一起,務實的推進方式大致會落在下面這個形式。

12.1 先把現行 Framework 側整好

  1. 收攏到 .NET Framework 4.7.2 以上,可以的話 4.8.1
  2. 把相依性往上升
  3. 重新檢視 packages.config
  4. 在可行範圍內收攏成 PackageReference 與 SDK 樣式
  5. 確認現行應用程式在那個狀態下確實能動

光是做完這一段,後段的差異就會少很多。

12.2 先把 shared library 救出來

接下來,把業務邏輯與共通合約收攏成 netstandard2.0 或 multi-target。 往上升的順序,基本上是 leaf-first。

12.3 應用程式本體要依 app model 改變策略

  • 類別庫 / 主控台 / 部分服務 比較容易直接推進
  • WinForms / WPF 維持 Windows 專用做現代化
  • ASP.NET MVC / Web API 小的話一次做完,重的話分段遷移
  • Web Forms 以置換畫面資產為前提,先把 shared logic 抽離出來
  • WCF 伺服器 先決定要維持 CoreWCF 還是重新設計成 gRPC

12.4 守住「不要一次全部做完」

特別想避開的是下面這些組合。

  • runtime 遷移 + ORM 全面更換
  • runtime 遷移 + 驗證基礎架構變更
  • runtime 遷移 + 雲端全面遷移
  • runtime 遷移 + 監控基礎架構變更
  • runtime 遷移 + UI 框架翻新

就算全部都需要,不要堆進同一個 sprint 通常會比較順利。

12.5 先取得測試與基準線再動手

至少要準備到這個程度再動手。

  • 單元測試
  • 主要業務流程的整合測試
  • 具代表性的畫面 / API 的快照式確認
  • 效能基準線
  • 主要日誌的查看方式
  • 回復程序

在無法判斷遷移後「什麼壞了」的狀態下往前推,是相當危險的。

動手前要準備的東西說明準備好測試與效能基準線、日誌的查看方式與回復程序再動手遷移,就能在遷移後判斷什麼壞了,而沒有準備就往前推很危險的圖。準備測試與基準線然後才動手遷移能判斷什麼壞了沒準備就往前推壞了也判斷不出來,很危險

圖 31: 遷移由動手前的準備決定,有基準線才能定位壞掉的地方。

13. 著手前檢核表

以可以直接貼進專案管理工具的形式放在這裡。

13.1 方針

  • 能用一句話說出為什麼要遷移
  • 這次的著地點是 Windows 專用的 modern .NET 還是 將來的跨平台化,已經決定了
  • 決定了 target 的 .NET 版本
  • 這次範圍中 不包含的東西(EF Core 化、驗證翻新、雲端全面遷移等)已經決定了

13.2 現行 .NET Framework 側的整備

  • 收攏到 .NET Framework 4.7.2 以上,可以的話 4.8.1
  • 把相依性升到較新的版本
  • 清查了有沒有 packages.config
  • 確認了能不能做 PackageReference 化
  • 確認了能不能做 SDK 樣式化
  • 現行應用程式在那個狀態下能建置、能啟動、能測試

13.3 應用程式種類與技術選型

  • 依類別庫 / 桌面 / Web / WCF 等種類分別區分了難度
  • 理解 WinForms / WPF 遷移後仍是 Windows 專用
  • 理解 ASP.NET Framework 是往 ASP.NET Core 的 app model 遷移
  • 把 Web Forms 的 UI 層置換放進了估算
  • WCF 分成用戶端與伺服器分別評估了

13.4 不支援的技術與要注意的 API

  • 清查了 AppDomain 相依
  • 清查了 Remoting / MarshalByRefObject / BeginInvoke / EndInvoke
  • 清查了 CAS / Security Transparency / COM+ / WF
  • 清查了 BinaryFormatter 相依
  • 清查了 System.Web 相依

13.5 Windows 專用相依

  • 清查了登錄檔、WMI、EventLog、Windows 服務、Directory Services 的使用
  • 清查了 System.Drawing.Common 的使用
  • 清查了 COM / ActiveX / Office Interop / P/Invoke / 原生 DLL
  • 確認了 x86 / x64 / ARM64 的限制

13.6 shared library 與資料存取

  • 把 shared library 分類成 業務邏輯 / 與 app model 緊貼層
  • 清查了哪些能改成 netstandard2.0
  • 清查了哪些函式庫需要 multi-target
  • 判斷了能不能留著 EF6 只先搬 runtime
  • 確認了 EDMX / 設計工具的相依

13.7 維運與 build

  • 做了設定檔的盤點
  • 確認了散發方式(IIS / 服務 / MSI / ClickOnce 等)
  • 確認了日誌 / 監控 / 權限 / 執行帳戶的前提
  • 確認了 CI/CD 與 build agent 是否需要更新
  • 做好了回復程序

14. 總結

從 .NET Framework 遷移到 .NET,重要的與其說是「用哪個指令搬」,不如說是 在著手前看穿哪些能直接搬、哪些是另一個問題。

要聚焦的話,就是這 6 點。

  • 遷移前先整理 .NET Framework 側
  • 依 app model 區分難度
  • 先清查不能用的技術
  • 決定要容忍多少 Windows 專用前提
  • 決定 shared library 怎麼切
  • 不要把 ORM、驗證、雲端遷移同時塞太多

遷移在最初的 1 週裡,估算的精確度會有相當大的差別。 反過來說,只要那 1 週能把論點整理好,後半就能相當程度地拉回一般開發的樣子。

最初 1 週的用法說明只要在最初的 1 週整理好哪些能直接搬、哪些是另一個問題的論點,估算的精確度就會提高,後半也能相當程度地拉回一般開發的圖。最初的 1 週整理論點估算的精確度提高後半能拉回一般開發分辨能直接搬的與另一個問題的

圖 32: 能不能把最初的 1 週用在盤點上,決定了整個遷移的精確度。

「先改成 net10.0 試試看」,作為探索並不壞。 但作為正式遷移,在那之前還有該看的地方。本文整理的正是那些地方。

15. 參考資料

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

從 .NET Framework 遷移到 .NET 之前,首先該做什麼?
在進入實作之前,先把 .NET Framework 側整理完。Microsoft 的官方指南也建議在移植前先升級到 .NET Framework 4.7.2 以上(實務上可以的話升到 4.8.1),把 packages.config 改成 PackageReference,收攏成 SDK 樣式的專案格式,並把相依性更新到較新的版本。先做完這一段,後段轉往 modern .NET 的差異會大幅減少,也比較容易分辨相容性問題的原因是「Framework 太舊」還是「轉成 .NET」。同時,AppDomain、Remoting、BinaryFormatter 這類不再支援的技術,也要在著手前先盤點出來。
有沒有可能判斷繼續留在 .NET Framework?
很正常地存在這種可能。只要 .NET Framework 4.8.1 跑在受支援的 Windows 上,就會持續獲得支援,所以並不是不立刻把全部搬到 modern .NET 就馬上有危險。如果符合這些條件:Web Forms 的畫面資產非常多、必須嚴格維持 WCF 伺服器相容性、對 Workflow Foundation 或 COM+ 的依賴很深、第三方的設計階段元件不支援,那麼暫時收攏到 4.8.1 穩定維運,另外開一條線擬定替換計畫,是合理的做法。不過限制仍然存在:走不出 Windows 專用,也比較難享受到新版 .NET 的效能與語言功能。
有哪些技術無法遷移到 .NET,或是特別容易卡住?
建立 AppDomain、.NET Remoting、CAS(程式碼存取安全性)、COM+(System.EnterpriseServices)、Workflow Foundation 在 modern .NET 上都不支援,是需要重新設計的紅燈。WCF 伺服器在內建的情況下無法直接運作,必須在 CoreWCF 與改設計成 gRPC / HTTP API 之間做選擇。BinaryFormatter 從 .NET 9 起執行階段不含實作,API 一律拋出例外,因此必須連持久化資料、ResX、剪貼簿與拖放都納入稽核。此外,packages.config 的 install.ps1 / XDT 轉換、原生 DLL、COM / ActiveX、隱含的 x86 前提,也都是建置能過但執行階段容易當掉的要注意之處。
把 WinForms 或 WPF 應用程式遷移到 .NET,就能跨平台了嗎?
不會。WinForms / WPF 即使遷移到 .NET,仍然是 Windows 專用框架。遷移換來的是 modern .NET 的執行階段、語言功能、SDK 樣式,以及與 CI/CD 的搭配度,並不會因此就能跑在 Linux 容器上。如果將來想走向 Linux 或容器化,必須及早盤點 System.Drawing.Common、登錄檔、WMI、EventLog、Windows 服務、COM、Office Interop 這類以 Windows 為前提的 API。反過來說,如果決定就留在 Windows 專用,也可以採取先用 Windows Compatibility Pack 把執行階段現代化的務實路線。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽