把 Windows Forms 專案移轉至 .NET Core 3.1

這篇筆記要整理的是我把一個現有的 Windows Forms 應用程式從 .NET Framework 4.x 移轉為 .NET Core 3.1 的過程。

簡介

工具版本:Visual Studio 2019 Preview v16.6.0 Preview 1

當我看到微軟部落格上的這篇文章:Updates on .NET Core Windows Forms designer,心情是興奮的,因為從去年就引頸期盼 Visual Studio 能夠支援 .NET Core 平台的 Windows Forms  應用程式開發(特別是 Form Designer,現在終於可用了!)。

於是我想,也許可以開始試試看,把既有的 Windows Forms 專案從 .NET Framework 4.7.2 移轉為 .NET Core 3.1。移轉過程可能會遇到一些問題,畢竟目前的 Visual Studio 2019 Preview 的版本也才 Preview 1 而已(或許還有 Preview 2、Preview 3)。
💬 雖然移轉至 .NET Core 3.1 的 Windows Forms 應用程式還是只能執行於 Windows 作業環境,但我的目的在於盡量使用一致的 target framework(.NET Standard 2.x 或 .NET Core 3.x)、一致的 .csproj 格式(SDK-based 格式),並且趁早把一些原本 .NET Framework 有提供、但 .NET Core 不支援的過時 API 改用新的 API,以便讓專案與時俱進,免得將來突然有一天要移轉成新版本(.NET 5?),碰到的問題可能會更多。

我先拿一個比較單純的 Windows Forms 應用程式來實驗。這個應用程式的原始碼包含以下專案:
  • 一個 Windows Forms 專案,target framework 是 .NET 4.7.2。
  • 數個 Class Library 專案,target framework 是 .NET Standard 2.0。
  • 第三方套件:SeriLog

移轉過程還算順利,只出現一個編譯錯誤:
'BrotliStream' is an ambiguous reference between 'Brotli.BrotliStream' and 'System.IO.Compression.BrotliStream'
解決方法很簡單,改用 .NET Core 提供的 System.IO.Compression.BrotliStream 就行了。(.NET Framework 4.x 沒有提供此類別,所以原本是使用第三方套件。)

有了好的開始,我便迫不及待把另一個 Windows Forms 應用程式拿來改看看了。先說結果:雖然移轉過程碰到比較多問題,最終還是移轉成功了。
這裡的「移轉成功」指的是整個 solution 中的所有 C# 專案都可以建置成功,而且初步測試應用程式的主要功能,都沒有發現問題。

先交代一下這個應用程式的基本資料:
  • 主程式:Windows Forms 專案,target framework 為 .NET Framework 4.7.2。
  • 一個命令列工具:Console 專案,target framework 為 .NET Framework 4.7.2。
  • 數個 DLL :Class Library 專案,target framework  為 .NET Framework 4.7.2。
  • 第三方套件:有的是自己寫的類別庫,還有一些是 Windows Forms 控制項(當然都是 .NET Framework 4.x)。

接著說明我的移轉步驟(已略過一些太過細節、很容易處理的問題)。

建立新分支

由於這個應用程式目前還正在服役,而我也不確定能否把全部的程式碼都順利移轉至 .NET Core 3.1,所以開了一個分支來專門進行移轉工作。分支名稱:upgrade-dotnetcore31。

後續的操作都是在 upgrade-dotnetcore31 分支上進行。

移轉主程式

先在目前的 solution 中加入一個新的 Windows Forms (.NET Core) 專案,準備取代原有的主程式專案。

原先的主程式專案名稱是 EasyBrailleEdit.csproj,那麼新的專案名稱就暫且命名為 EasyBrailleEditCore.csproj。專案建立完成後,開啟 .csproj .csproj 原始碼,可以發現它跟舊專案的明顯差異:

值得留意的是第 5 行和第 6 行。

把專案範本預設加入的 Form1.cs 刪除,再把原先的 Windows Forms 專案裡面的檔案都加入到這個新專案裡。大部分的操作都是在 Solution Explorer 視窗裡,先把要加入的檔案都選好,然後以滑鼠拖放的方式把這些選取的檔案拉到新專案裡面。把檔案加入新專案之後,接著就把 EasyBrailleEdit.csproj 從方案中移除,然後把新專案的名稱從 EasyBrailleEditCore.csproj 改為 EasyBrailleEdit.csproj,以取代原有的專案。

保險起見,App.config 是以手動編輯的方式,只把需要的內容貼到新專案的 App.config,以免舊專案裡面有些設定不適用於新專案。另外,專案參考也都是採手動方式、對照著舊專案逐一加入新專案。

加入專案參考和第三方套件

自己寫的 Class Library 容易處理,直接加入專案參考即可。看起來比較會碰到問題的,是一些第三方套件,特別是這幾個與 Windows Forms UI 有關的套件:
從 Solution Explorer 視窗裡面可以看到一些代表警告的黃色三角形:


暫且不理會它們,等到整個方案都能順利通過編譯,再來看看有哪些地方需要處理。

修改 Main 方法



修正編譯錯誤

從編譯錯誤訊息中發現底下兩個類別,是 .NET Framework 4.x 有,但是 .NET Core 沒有的:
  • ContextMenu
  • MenuItem
ContextMenu 已經由 ContextMenuStrip 取代,而 MenuItemToolStripMenuItem 取代,所以這個部分得稍微改一下程式碼(有些屬性名稱也改了,但不難處理)。

另一類錯誤是類別名稱衝突,例如第三方套件 SourceGrid 的 Range 類別與 System.Range 名稱衝突。這也不是問題,在程式中改用 full qualified name 即可。

編譯成功後,試著執行看看。結果主視窗一開啟就發生錯誤:


查出問題原因出在 jacobslusser.ScintillaNET 套件。此套件的 target framework 是 .NET 4.0。還好有一位仁兄 fork 這個專案,把它改成相容於 .NET Framework 4.6.1 和 .NET Core 3.1,而且還提供了 NuGet 套件,名稱是 fernandreu.ScintillaNET。

把 jacobslusser.ScintillaNET 套件從專案中移除,改用 fernandreu.ScintillaNET 之後,上述執行時期的錯誤便解決了。

移轉 Class Library 專案

這個步驟比起前面的主程式來得輕鬆許多。主要是因為,以 .NET Framework 4.x 為 target framework 的 Class Library 專案,我在幾個月前就已經把它們的 .csproj 檔案從傳統格式改為 SDK-based 格式(純手工作業,過程不太好玩)。所以,我現在只是單純把 .csproj 檔案裡面的 <TargetFramework> 元素內容從 "net472" 改為 "netcoreapp3.1" 而已。

後續工作

到目前為止,移轉過程比我原先想像的更為順利。唯一的隱憂,就是那個第三方套件 SourceGrid。目前建置專案時,只剩下三個編譯警告,都與它有關。警告訊息的內容是:
Package 'SourceGrid 4.4.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.1'. This package may not be fully compatible with your project.
還好它是開放原始碼專案。要是以後發現相容性的問題,還是有可能自行修改程式碼來解決(當然還是得等到 Visual Studio 支援 .NET Core 3.x 的 Windows Forms 類別庫專案)。

另外,我還發現 Windows Forms 專案原本的資源檔案(.resx)沒有作用。研判可能尚未支援或者是個 bug,所以到 developercommunity.visualstudio.com 反映此問題,詳見:Windows Forms (.NET Core) project cannot use images from a resource file

參考資料

技術提供:Blogger.