在升級至 ASP.NET Core 3.x 的時候,為了讓專案繼續支援 .NET Core 2.x 的環境而碰到一些問題。這篇筆記要整理其中一個問題的由來,並說明如何使用條件編譯與類型別名來解決 ASP.NET Core 專案同時支援 2.x 和 3.x 所衍生的問題。
內容大綱:
比如說,ASP.NET Core 程式裡面用的是
為了減少命名混亂和衝突的困擾,ASP.NET Core 3.0 開始統整這些概念相似但具有不同實作的組件。然而,在改革進步的同時,總得考慮這些新的改變是否會影響既有的 ASP.NET Core 2.x 應用程式,也就是說,必須向後相容(backward compatibility)。
即便有考慮跟舊版相容,升級至 ASP.NET Core 3.x 的過程還是難免碰到一些麻煩,症狀依應用程式的複雜度而不同:
本文內容偏重在比較麻煩的情境,也就是支援多種 framework 版本(multi-targeting)的專案在升級至 ASP.NET Core 3.x 的時候可能碰到的問題。
拿我最近碰到的狀況為例,當我為 ASP.NET Core 專案的 target frameworks 增加 netcoreapp3.1 之後,便開始出現編譯警告。底下先列出我的 csproj 部分內容:
你可以看到,支援的 frameworks 包括 .NET Core 2.x 和 3.x,以及 .NET Framework 4.6.2。
編譯專案時,好幾個 .cs 檔案都出現編譯警告,訊息如下:
咦?那還不簡單,只要把程式裡所有的
對於單一 target framework 的專案來說,差不多就是這麼簡單。然而支援多種 framework 的專案則比較麻煩,不是單純替換字串就能解決(稍後會說明解決方法)。
過時的
我不是很在意這些差異,畢竟它已經被打上「過時」的記號。重點是如何處理剛才的編譯警告。
另一方面,新的
先看一下修改前的程式碼:
我的作法是,在程式裡面使用 ASP.NET Core 3 的新型別:
用白話來解釋第 3 行至第 7 行程式碼,就是:
除了
欲了解更多細節,可參考微軟文件:SDK 樣式專案中的目標框架。
值得一提的是,ASP.NET Core 2.x 的中繼套件(metapackage)Microsoft.AspNetCore.App 在 ASP.NETCore 3.0 以後已經不復存在。也就是說,並沒有 「Microsoft.AspNetCore.App v3.0.0」這樣的東西。那些 ASP.NET 應用程式中的共用套件,在 ASP.NET Core 3.0 以後都不會再出現於 nuget.org,而已經變成了框架內定的套件了。好處是,csproj 檔案變得清爽些,開發人員也更輕鬆一些,不用一直為那些個別套件的多種版本組合而傷腦筋。
比較麻煩的情況,是在專案升級至 ASP.NET Core 3.x 時還要持續支援 ASP.NET Core 2.x(甚至更早之前的版本),再加上第三方套件所衍生的問題,往往得費一番工夫來處理。如果早晚都要面對,還是有時間就盡早動手升級吧(開一個新的分支來處理)。
Happy coding!
內容大綱:
- 一點歷史
- 兼容並蓄、異中求同
- 使用條件編譯與類型別名
- 在 csproj 檔案中引用不同版本的套件
- 結語
一點歷史
ASP.NET Core 2.1 引進了 Generic Host 來為那些不是基於 HTTP 的應用程式提供應用程式組態(configuration)、相依性注入(dependency injection)和紀錄(logging)等功能,而這些功能是透過 Microsoft.Extensions.* 相關組件來提供。如此一來,這些功能便有了兩份實作,而且有著類似的名稱(但命名空間不同)。比如說,ASP.NET Core 程式裡面用的是
WebHostBuilder
,在非 HTTP 的應用程式裡則是用 HostBuilder
;這兩個類別所實作的介面分別是 IWebHostBuilder
和 IHostBuilder
,而這兩個介面(與其實作)是各自獨立的,彼此沒有任何繼承關係。IHostBuilder
來自 Microsoft.Extensions.Hosting
命名空間,而 IWebHostBuilder
來自 Microsoft.AspNetCore.Hosting
。(看完這段,你是不是覺得有點暈?)為了減少命名混亂和衝突的困擾,ASP.NET Core 3.0 開始統整這些概念相似但具有不同實作的組件。然而,在改革進步的同時,總得考慮這些新的改變是否會影響既有的 ASP.NET Core 2.x 應用程式,也就是說,必須向後相容(backward compatibility)。
即便有考慮跟舊版相容,升級至 ASP.NET Core 3.x 的過程還是難免碰到一些麻煩,症狀依應用程式的複雜度而不同:
- 如果你的專案極少(或完全沒有)使用第三方套件,而且 target framework 只有一個(例如 netcoreapp2.1),那麼直接升級至 ASP.NET Core 3.x 應該是個簡單任務。
- 如果你的專案使用了不少第三方套件,而且 target framework 只有一個,那麼主要就是看看那些相依套件是否能順利運行於 .NET Core 3.x 的環境。一般而言,升級套件時多少會需要修改一下既有的程式碼,但通常不是大問題(只是根據我的有限經驗來說)。
- 如果你的 ASP.NET 程式使用了不少第三方套件,而且又必須支援多種 target frameworks(例如 netcoreapp2.1、netcoreapp3.1),這就會碰到比較多麻煩了。
本文內容偏重在比較麻煩的情境,也就是支援多種 framework 版本(multi-targeting)的專案在升級至 ASP.NET Core 3.x 的時候可能碰到的問題。
兼容並蓄、異中求同
前面提到,如果 ASP.NET 程式使用了不少第三方套件,而且又得同時支援多種版本的 frameworks(例如 netcoreapp2.1、netcoreapp3.1),就會碰到比較多麻煩,因為 ASP.NET Core 3.0 開始統整一些相似概念的型別,以便消除重複和避免混淆,然而又得向後相容,最終只好在新版本當中推出新型別。那麼,既然 3.x 的某些型別名稱改成跟 2.x 不同(原本 2.x 的型別當然還是保留著),那麼當我們想要讓 ASP.NET Core 應用程式同時支援 2.x 和 3.x 的環境,勢必得費一番工夫。拿我最近碰到的狀況為例,當我為 ASP.NET Core 專案的 target frameworks 增加 netcoreapp3.1 之後,便開始出現編譯警告。底下先列出我的 csproj 部分內容:
你可以看到,支援的 frameworks 包括 .NET Core 2.x 和 3.x,以及 .NET Framework 4.6.2。
編譯專案時,好幾個 .cs 檔案都出現編譯警告,訊息如下:
Warning CS0618: 'IHostingEnvironment' is obsolete: 'This type is obsolete and will be removed in a future version. The recommended alternative is Microsoft.AspNetCore.Hosting.IWebHostEnvironment.' MyApp.WebApi (netcoreapp3.1)意思是,在針對 netcoreapp3.1 環境來編譯專案 MyAspNetApp.WebApi.csproj 時,發現程式裡面使用了過時的
IHostingEnvironment
型別,未來可能會拿掉它,故建議改用 IWebHostEnvironment
。咦?那還不簡單,只要把程式裡所有的
IHostingEnvironment
替換成新的 IWebHostEnvironment
不就結了嗎?對於單一 target framework 的專案來說,差不多就是這麼簡單。然而支援多種 framework 的專案則比較麻煩,不是單純替換字串就能解決(稍後會說明解決方法)。
過時的
IHostingEnvironment
有點討厭,因為它存在於兩個命名空間:Microsoft.AspNetCore.Hosting
和 Microsoft.Extensions.Hosting
都各有一個 IHostingEnvironment
,而且彼此雖然長得相似,卻互不相關(沒有繼承關係)。以下程式碼可以看出兩者的差異:我不是很在意這些差異,畢竟它已經被打上「過時」的記號。重點是如何處理剛才的編譯警告。
另一方面,新的
IWebHostEnvironment
介面隸屬命名空間 Microsoft.AspNetCore.Hosting
。該介面繼承自 IHostEnvironment
,隸屬另一個命名空間:Microsoft.Extensions.Hosting
。從這個小地方也可以發現,ASP.NET Core 3.0 以後的 hosting 功能已經是建立在非 HTTP(即開頭提過的 Generic Host)的基礎之上,不再是互不相干了。使用條件編譯與類型別名
如果 ASP.NET 專案無需考慮 netcoreapp2.x,而只支援 netcoreapp3.x,那麼很簡單,只要把程式裡面的IHostingEnvironment
全部替換成 IWebHostEnvironment
或其父代介面 IHostEnvironment
就行了。然而,我的專案需要同時支援 netcoreapp2.x 和 netcoreapp3.x,還是得在程式裡動點手腳。先看一下修改前的程式碼:
我的作法是,在程式裡面使用 ASP.NET Core 3 的新型別:
IHostEnvironment
,並且用類型別名(alias)來讓程式碼同時支援 netcoreapp2.x 和 3.x 的編譯。如下所示:用白話來解釋第 3 行至第 7 行程式碼,就是:
- 在針對 netcoreapp3.1 環境來編譯時,引用新的命名空間
Microsoft.Extensions.Hosting
(這樣才有新的介面IHostEnvironment
可用)。 - 若是針對其他 framework 來編譯,則給舊版的介面
Microsoft.AspNetCore.Hosting.IHostingEnvironment
添加新的別名:IHostEnvironment
。
IHostEnvironment
。底下用 kdiff3 來呈現程式碼修改前後的差異:除了
IHostingEnvironment
介面,ASP.NET Core 3 還有新的 IHostApplicationLifetime
可取代過時的 IApplicationLifetime
。我也是用同樣的方式來處理,底下是 GitHub 的差異比對結果:在 csproj 檔案中引用不同版本的套件
除了在程式碼裡面使用條件編譯,你可能還會需要在 .csproj 檔案裏面使用條件式來引用不同版本的套件(理由同上,都是為了向後相容、支援多個 target frameworks)。像這樣:欲了解更多細節,可參考微軟文件:SDK 樣式專案中的目標框架。
值得一提的是,ASP.NET Core 2.x 的中繼套件(metapackage)Microsoft.AspNetCore.App 在 ASP.NETCore 3.0 以後已經不復存在。也就是說,並沒有 「Microsoft.AspNetCore.App v3.0.0」這樣的東西。那些 ASP.NET 應用程式中的共用套件,在 ASP.NET Core 3.0 以後都不會再出現於 nuget.org,而已經變成了框架內定的套件了。好處是,csproj 檔案變得清爽些,開發人員也更輕鬆一些,不用一直為那些個別套件的多種版本組合而傷腦筋。
結語
在升級的路上,如果是單一 target framework,事情會簡單許多,因為那些已經過時的套件或類別都可以直接丟棄,或改成新的名稱。比較麻煩的情況,是在專案升級至 ASP.NET Core 3.x 時還要持續支援 ASP.NET Core 2.x(甚至更早之前的版本),再加上第三方套件所衍生的問題,往往得費一番工夫來處理。如果早晚都要面對,還是有時間就盡早動手升級吧(開一個新的分支來處理)。
Happy coding!
沒有留言: