公司的項目一直采用.NET框架來開發Web項目。目前基礎類庫均為.NET Framework 4.6.2版本。Caching, Logging,DependencyInjection,Configuration等基礎設施相關的依賴庫一直和官方保持同步,目前是1.1版本。.NET Core越來越趨於穩定,新的開發工具也在三月份發布。因此,計划將.NET Framework移植至.NET Core/Strandard。目的是使基於.NET開發的Web應用可以跨平台運行。
按應用場景將公司的項目分為基礎類庫,基礎服務和應用項目。基礎類庫以包的形式提供各類基礎功能。基礎服務通過Wcf項目搭建或者通過Web API項目搭建。應用項目則是Web Mvc項目為主。基礎類庫和基礎服務是以一個一個解決方案的形式存在。每個解決方案的結構,包含一個或者多個類庫項目,一個或多個控制台項目,它們分別用於功能實現、單元測試、功能演示。如果全部要移植,那么優先級應該是基礎類庫 -> 基礎服務 -> 應用項目。此次移植的對象是基礎類庫。
基礎類庫最終會以包的形式通過NuGet發布出去,目前只面向.NET Framework框架。移植的目標之一,是讓包也能被面向.NET Core、.NET Standard框架的項目引用。結合官方資料,我選擇了直接遷移的方案。即直接將項目文件轉換為新的基於.NET Core的項目文件。下面詳細說明移植的細節。
1. 新建基於.NET Core的項目。
首先重命名現有項目文件*.csproj為*.Net46.csproj。然后使用VS2017新建一個新的基於.NET Core的項目,項目類型可以是“類庫(.Net Core)”或者“類庫(.Net Standard)”。注意,VS2017會提示存在同名目錄,所以創建時可以輸入一個不同的名稱,然后手工調整回來。

2. 編輯項目文件,使之支持面向多個目標框架。
通過VS2017 RC新建的項目,“類庫(.Net Core)”或者“類庫(.Net Standard)”,默認只有一個目標框架。我們可以編輯項目文件,使之支持面向多個目標框架。如支持目標框架為.NET Standard 1.4、.Net Core App 1.0和.NET Framework 4.5,則這樣來修改。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.4;netcoreapp1.0;net45</TargetFrameworks>
</PropertyGroup>
</Project>
注意,官方文檔中提供了.NET支持的目標框架列表,你可以查詢更多其他的目標框架。如果要兼容較低版本的框架,則目標框架版本不宜設置過高。如“net45”可用於.NET Framework 4.5 ~ 4.6.2等版本。如“net46”則只能用於.NET Framework 4.6 ~ 4.6.2等版本。
3. 修改應用程序代碼相關API,使之支持多個目標框架。
a. 因目標框架提供的API不相同。故必要時可添加條件編譯符號以便支持不同的運行時版本。
以下是常見的條件編譯符號列表。
.NET Framework 2.0 --> NET20
.NET Framework 3.5 --> NET35
.NET Framework 4.0 --> NET40
.NET Framework 4.5 --> NET45
.NET Framework 4.5.1 --> NET451
.NET Framework 4.5.2 --> NET452
.NET Framework 4.6 --> NET46
.NET Framework 4.6.1 --> NET461
.NET Framework 4.6.2 --> NET462
.NET Standard 1.0 --> NETSTANDARD1_0
.NET Standard 1.1 --> NETSTANDARD1_1
.NET Standard 1.2 --> NETSTANDARD1_2
.NET Standard 1.3 --> NETSTANDARD1_3
.NET Standard 1.4 --> NETSTANDARD1_4
.NET Standard 1.5 --> NETSTANDARD1_5
.NET Standard 1.6 --> NETSTANDARD1_6
關於條件編譯符號的應用,如以下代碼:
using System; using System.IO; namespace Baza.NetStandardTester { public class PathHelper { public string BaseDirectory { get; set; } public PathHelper() { #if NET45 BaseDirectory = AppDomain.CurrentDomain.BaseDirectory; #endif } public string GetRootedPath(string path) { string rootedPath = path ?? string.Empty; if (!Path.IsPathRooted(rootedPath)) { if (string.IsNullOrEmpty(BaseDirectory)) throw new ArgumentNullException("請先設置BaseDirectory屬性"); rootedPath = Path.Combine(BaseDirectory, rootedPath); } string directory = Path.GetDirectoryName(rootedPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } return rootedPath; } } }
代碼說明,PathHelper提供GetRootedPath方法用於根據相對路徑計算出絕對路徑。當運行時為.NET Core時,BaseDirectory屬性需要手動設置。當運行時為.NET Framework 4.5時,由構造器對BaseDirectory屬性進行賦值。注意System.AppDomain.CurrentDomain.BaseDirectory用於獲取托管程序執行路徑,類AppDomain只存在於.NET Framework中。
b. .NET Standard是個基於包的框架。當你需要某個API時,如IDataReader,你需要安裝System.Data.Common包。如果是使用.NET Framework,則在命名空間System.Data下可以找到IDataReader而無需按照包。借助https://packagesearch.azurewebsites.net/工具,你可以快速定位某個API在哪個包中。
c. 基於.NET Core的項目,包版本號和其他元數據,都存儲在*.csproj中,不會使用AssemblyInfo.cs文件,即移植時,這個文件可以刪除。但是.NET Framework項目還是會繼續使用該文件。
4. 同一解決方案,類庫間的引用策略。
在引用類庫時,要注意目標框架的兼容問題。如,“類庫(.Net Standard)”項目,能夠被.NET Core App、.NET Framework和其他.NET Standard項目引用。這個是因為.NET Core App和.NET Framework都支持相應版本的.NET標准庫。下表顯示了支持 .NET 標准庫的整套 .NET 運行時。
| 平台名稱 | Alias | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| .NET Standard | netstandard | 1.0 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5 | 1.6 | 2.0 |
| .NET 核心 | netcoreapp | → | → | → | → | → | → | 1.0 | vNext |
| .NET Framework | net | → | 4.5 | 4.5.1 | 4.6 | 4.6.1 | 4.6.2 | vNext | 4.6.1 |
| Mono/Xamarin 平台 | → | → | → | → | → | → | → | vNext | |
| 通用 Windows 平台 | uap | → | → | → | → | 10.0 | → | → | vNext |
| Windows | win | → | 8.0 | 8.1 | |||||
| Windows Phone | wpa | → | → | 8.1 | |||||
| Windows Phone Silverlight | wp | 8.0 |
注意,如果項目是面向多目標框架的,那么引用類庫時,被引用類庫也要支持面向多目標框架。
5. 單元測試
如果是使用.NET Framework類庫項目來存放單元測試代碼,那么可能會遇到一點問題。在VS2017 RC中,測試資源管理器無法識別出這些測試單元。通過新建“單元測試項目(.NET Framework)”,將生成的同名*.csproj覆蓋原來的項目文件,測試管理器即可識別出來。

5. MSBuild自動編譯新的解決方案
Windows下,按Release配置對整個解決方案編譯。
"c:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" XXX.sln /p:Configuration=Release;Platform="Any CPU"
會在相關類庫根目錄下\bin\Release目錄中,生成net46和netstandard1.6兩個目錄。
6. 使用dotnet-nuget-push發布包
使用vs2017打包時,只需右擊要打包的項目,選擇“打包”,即可在.\bin\Debug或.\bin\Release下生成XXX.0[.0].0.nupkg文件,然后將這個文件.nupkg上傳至nuget.org中。通過調用dotnet-nuget-push可以自動化這個發布過程,因此這種方式會更加方便。
dotnet nuget push XXX.0[.0].0.nupkg -k 4003d786-0000-4004-bfdf-c4f3e8ef9b3a -s http://customsource/
k:服務器的 API 密鑰 s:服務器 URL,如發布到nuget.org,則可以這樣寫。
dotnet nuget push XXX.0[.0].0.nupkg -k 4003d786-0000-4004-bfdf-c4f3e8ef9b3a -s https://www.nuget.org/api/v2/package
通常在windows下,會將dotnet-nuget-push寫在批處理文件中來完成基本類庫的部署工作。
總結
通過此方案遷移后,最終保留新的解決方案和項目文件,舊的解決方案和項目文件在移植的過程中被刪除。之后將按照新的解決方案來跨平台開發。基本類庫的移植工作就介紹到這里。源代碼的移植將是個挑戰。譬如部分源碼所引用的API在.NET Core框架下不存在時如何處理?另外,基礎服務和Web Mvc項目的移植,因為要部署到linux中。也將會遇到各種問題。
參考資源
1. 組織項目以支持 .NET Framework 和 .NET Core
3. packagesearch.azurewebsites.net
4. .NET 標准庫
