[WPF]是時候將WPF控制項庫從.Net Framework升級到.NET Core 3.1

1. 升級到Core的好處

去年中我曾考慮將我的控制項庫項目Kino.Toolkit.Wpf升級到.NET Core,不過很快放棄了,因為當時.NET Core是預覽版,編譯WPF還需要使用最新的Visual Studio 2019,這樣作為一個教學項目不夠友好。到了今天.NET Core 3.1都出來了,已經正式支援WPF和Winform,Visual Studio 2019也已經普及,我覺得應該是時候將我的控制項庫升級到.NET Core。那麼現在是WPF正式遷移到.NET Core的好時機嗎?我認為還不是,把一個成熟的WPF程式遷移到.NET Core風險任然較大,而且不見得有多少好處。但對各種WPF類庫/控制項庫來說情況又不一樣了,為了可以滿足更多的用戶,讓控制項庫可以同時支援.NET Framework和.NET Core十分重要;而且通常類庫對其它組件的依賴較少,升級的風險沒那麼大。所以要玩.NET Core的WPF,從類庫/控制項庫開始是一個好的選擇。

具體來說,讓WPF控制項庫升級到.NET Core具體來說有以下的好處:

  • 巨大的時髦值,最近WPF開發時髦值很低,.NET Core是我們為數不多可以蹭到時髦值、面向時髦值編程的機會。
  • 新的csproj文件,順便升級到新的SDK-style csproj文件有很多好處,包括更簡潔可讀的文件,新的NuGet引用方式,可以指定多個開發框架等。
  • 更方便打包Nuget。

升級到.NET Core 3.1有以下步驟:

  • 分析可移植性
  • 遷移到 NuGet 引用
  • 遷移csproj項目文件

這篇文章我會以我的Kino.Toolkit.Wpf項目作為示例,master分支不升級,而core升級到core 3.1以作比較。需要注意的是,WPF控制項庫的升級和其它.NET項目的升級有一點出入,這篇文章的升級方式不一定適合其它.NET Core項目。

2. .NET 可移植性分析

在升級前,保險起見需要使用.NET 可移植性分析器分析項目在目標.NET平台上的可移植性。安裝.NET Portability Analyzer這個Visual Studio的擴展後在Visual Studio的解決方案資源管理器窗口選中要分析的項目,右鍵選擇「Analyze Project Portability」:

在結果窗口選擇「Open Report」:

結果將以Excel的方式顯示,像這種小項目一般不會出現什麼問題,圖個安心:

3. 遷移到 PackageReference NuGet 引用

引用了Nuget包的舊.NET Framework項目會將引用的Nuget資訊記錄在packages.config文件中,例如在示例的項目中,這個文件的內容如下:

<?xml version="1.0" encoding="utf-8"?>  <packages>    <package id="Microsoft.CodeAnalysis.FxCopAnalyzers" version="2.9.8" targetFramework="net45" developmentDependency="true" />    <package id="Microsoft.CodeAnalysis.VersionCheckAnalyzer" version="2.9.8" targetFramework="net45" developmentDependency="true" />    <package id="Microsoft.CodeQuality.Analyzers" version="2.9.8" targetFramework="net45" developmentDependency="true" />    <package id="Microsoft.NetCore.Analyzers" version="2.9.8" targetFramework="net45" developmentDependency="true" />    <package id="Microsoft.NetFramework.Analyzers" version="2.9.8" targetFramework="net45" developmentDependency="true" />    <package id="Microsoft.Xaml.Behaviors.Wpf" version="1.1.19" targetFramework="net45" />  </packages>  

新的SDK-Style項目文件使用PackageReference節點記錄Nuget的引用資訊,這樣做的好處包括精簡內容與以及不再需要額外的packages.config文件,所以我們必須將packages.config遷移到 PackageReference。要遷移到PackageReference,先儘可能升級引用的Nuget包,然後選中項目中的packages.config,在右鍵菜單中選中「將 packages.config 遷移到 PackageReference」:

在彈出的對話框會列出頂級的依賴項和傳遞的依賴項,還會詢問是否將後者升級到頂級依賴項,這個項目無需做任何改變,直接點擊「確定」:

遷移完成後會得到一個報告:

打開Kino.Toolkit.Wpf.csproj,會發現少了些東西,但多了下面這段,這段就是經過精簡的Nuget引用,在「管理Nuget程式包」的頁面也可以看到已安裝的Nuget變少了:

完成這一步後還原Nuget包,該升級的升級,運行下確認升級沒有出錯,然後進行下一步。

4. 遷移csproj項目文件

接下來需要遷移csproj項目文件到新的SDK-Style格式,不過在那以前好歹先確保自己已經安裝了.NET Core 3.1 SDK,隨便新建一個WPF (.NET Core)項目,這裡我選擇了自定義控制項庫項目:

生成的項目的csproj項目文件如下:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">      <PropertyGroup>      <TargetFramework>netcoreapp3.1</TargetFramework>      <UseWPF>true</UseWPF>    </PropertyGroup>    </Project>  

其中SDK 是一組可生成 .NET Core 程式碼的 MSBuild 任務和目標,Sdk="Microsoft.NET.Sdk.WindowsDesktop"標識這是一個.NET Core的WinForms或WPF項目。

PropertyGroup這一節表明這是個.NET Core 3.1項目,並使用WPF。如果是應用程式項目的話還需要<OutputType>WinExe</OutputType>,因為這是個類庫項目所以缺少了這一節。

為了可以支援多個框架,需要將<TargetFramework>這一節改為下面內容,注意TargetFramework變為TargetFrameworks,因為從單一框架變成多個框架。

<TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>  

現在可以把這些內容複製到Kino.Toolkit.Wpf.csproj,加上前面提到的<PackageReference>節點的內容,完整內容如下:

  <PropertyGroup>      <TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>      <UseWPF>true</UseWPF>    </PropertyGroup>      <ItemGroup>      <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">        <Version>2.9.8</Version>        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>        <PrivateAssets>all</PrivateAssets>      </PackageReference>      <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf">        <Version>1.1.19</Version>      </PackageReference>    </ItemGroup>    </Project>  

重新載入項目,還原Nuget包重新編譯等一系列操作都完成後,可以見到項目已經完成遷移了:

5. 處理其它問題

遷移項目文件後會有一些問題,首先是以前從項目中排除的文件又包含在項目里了,畢竟以前那麼複雜的項目文件可不是吃素的,這麼簡單粗暴遷移過來總會丟一些內容。重新將他們從項目中排除,項目文件多了以下這些內容,以表明這些文件都是多餘的(如果文件真是多餘的也可以直接刪掉):

<ItemGroup>    <Compile Remove="Class1.cs" />    <Compile Remove="SkeletonScreenDispatcherContainer.cs" />  </ItemGroup>  <ItemGroup>    <None Remove="ClassDiagram1.cd" />  </ItemGroup>  

AssemblyInfo.cs這個文件有很多版本號之類的資訊,現在都在項目文件中聲明,所以這些資訊全都變得多餘,會引起編譯錯誤,全部刪掉只保留下面這些就好:

// [assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]  [assembly: ThemeInfo(      ResourceDictionaryLocation.None,      ResourceDictionaryLocation.SourceAssembly)  ]      [assembly: XmlnsPrefix("https://github.com/DinoChan/Kino.Toolkit.Wpf", "kino")]  [assembly: XmlnsDefinition("https://github.com/DinoChan/Kino.Toolkit.Wpf", "Kino.Toolkit.Wpf")]  [assembly: XmlnsDefinition("https://github.com/DinoChan/Kino.Toolkit.Wpf", "Kino.Toolkit.Wpf.Primitives")]  

其中ThemeInfo指示項目使用默認的ThemesGeneric.xaml主題文件,對WPF項目是必不可少。XmlnsPrefix等內容是為了方便在XAML內引用這個項目,具體可見命名空間這一段內容。

然後重新填一填應用程式和打包資訊,可以看到項目文件中多了不少內容:

<PropertyGroup>    <TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>    <UseWPF>true</UseWPF>    <ApplicationIcon>AssetsImageskino.ico</ApplicationIcon>    <Version>1.6.0</Version>    <Copyright>Copyright ©  2019</Copyright>    <PackageLicenseExpression>https://raw.githubusercontent.com/DinoChan/Kino.Toolkit.Wpf/master/LICENSE</PackageLicenseExpression>    <PackageProjectUrl>https://github.com/DinoChan/Kino.Toolkit.Wpf</PackageProjectUrl>    <PackageIcon>Logo.png</PackageIcon>    <RepositoryUrl>https://github.com/DinoChan/Kino.Toolkit.Wpf</RepositoryUrl>  <PropertyGroup>    <TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>    <UseWPF>true</UseWPF>    <ApplicationIcon>AssetsImageskino.ico</ApplicationIcon>    <Version>1.6.0</Version>    <Copyright>Copyright ©  2019</Copyright>    <PackageLicenseExpression></PackageLicenseExpression>    <PackageProjectUrl>https://github.com/DinoChan/Kino.Toolkit.Wpf</PackageProjectUrl>    <PackageIcon>Logo.png</PackageIcon>    <RepositoryUrl>https://github.com/DinoChan/Kino.Toolkit.Wpf</RepositoryUrl>    <PackageTags>WPF Control Toolkit Xaml</PackageTags>    <Description>A set of wpf toolkit.</Description>    <NeutralLanguage>en-US</NeutralLanguage>  </PropertyGroup>  

具體的打包成Nuget的過程可以參考林德熙的這篇文章:

VisualStudio 使用新項目格式快速打出 Nuget 包

6. 結語

實際上WPF項目要遷移到.NET Core會複雜很多,目前我也只是在控制項庫上嘗試。但換成新SDK-Style項目格式沒什麼壞處,可以放手一拼(只要不我讓我負責任)。

有些項目可能還需要安裝Microsoft.Windows.Compatibility,更多的資訊請看下面給出的參考鏈接。

7. 參考

Migrating WPF Apps to .NET Core 3.0 – WPF _ Microsoft Docs

.NET Core 的 csproj 格式的新增內容 – .NET Core CLI _ Microsoft Docs

從 .NET Framework 移植到 .NET Core – .NET Core _ Microsoft Docs

將 Contoso Expenses 應用遷移到 .NET Core 3 _ Microsoft Docs

.NET 可移植性分析器 – .NET _ Microsoft Docs

將傳統 WPF 程式遷移到 DotNetCore 3.0 – hippieZhou – 部落格園

將基於 .NET Framework 的 WPF 項目遷移到基於 .NET Core 3 – walterlv

VisualStudio 使用新項目格式快速打出 Nuget 包