rksoftware

Visual Studio とか C# とかが好きです

WPF アプリケーションを .NET Core 3.0 に移植する

先日、毎日チェックしている RSS で次の記事を見つけました。

WPF と WinForms の .NET Core 3.0 移行記事です。とても関心のある話題なのでなぞってやってみました。
いずれ実際に移行をする時のために、私は英語力皆無ですしやってみた結果を日本語で自分なりに書いておきます。

■ 簡単な例/難しい例

今回の記事はサードパーティの UI コンポーネントなどに存じていない小規模なアプリを移植する例とのことです。
複雑なアプリについては別記事を書いてくれるそうです。

■ 手順の概要

長い記事ではないですが、分かっている人向けに手順の概要をこれだけで完全に理解した方はこの先は読まなくて大丈夫だと思います。

  1. .NET Portability Analyzer を使って、使用しているパッケージ/API が .NET Core で使えるか確認する。
  2. .NET Core で使えない場合、使えるものに置き換える。
  3. packages.config を PackageReference 形式に変更する。
  4. .csproj を SDK スタイルに書き換える。
  5. を netcoreapp3.0 に変更する。

簡単なお仕事です。

■ 今回移植してみるアプリ

WPF で UI にはボタンが一つあるだけ、クリックするとメッセージボックスを表示するだけのアプリです。

using System.Windows;

public partial class MainWindow : Window
{
    public MainWindow() { InitializeComponent(); }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var ac = System.IO.File.GetAccessControl(@"nanika fairuno pasu");
        MessageBox.Show(ac.ToString());
    }
}
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="Button" Click="Button_Click"/>
    </Grid>
</Window>

敢えて .NET Core 3.0 で使えない System.IO.File.GetAccessControl メソッドを書いています。このメソッドがどうなるかも確認してみます。
また、パッケージや別プロジェクトを参照している場合の状況も分かるように、無駄に EntityFramework と Newtonsoft.Json のパッケージをインストールし、ソリューション内の ClassLibrary1 という別プロジェクトも参照しています。
この .NET Core にないメソッドを実際に試すのは私の独自研究です。

■ .NET Portability Analyzer で、パッケージ/API が .NET Core で使えるか確認する

元記事内に書かれているダウンロードリンクから .NET Portability Analyzer をダウンロードします。zip ファイルがダウンロードされるので展開して中の .exe を実行します。

f:id:rksoftware:20190615123050j:plain

Browse ボタンから選択またはテキストボックスへの入力で移行したいアプリのあるフォルダを設定します。設定するのは移行したいアプリの .exe ではなく .exe のあるフォルダです。解析結果は Excel で作成されます。作成される場所は一時フォルダなのであとでまた見たいならばどこかに移動して置くと良いと思います。

解析結果の Excel ファイル
f:id:rksoftware:20190615123153j:plain

C 列が解析の結果です。使っている API がすべて .NET Core で使えるなら 100。使えない API が使われていと 100 より小さくなります。
100 より小さい場合は Excel の Details タブを確認すると .NET Core で使えない API が一覧されています。ここを見て .NET Core で使える API に置き換えます。

f:id:rksoftware:20190615123314j:plain

おススメの置き換え先 API がある場合は、Recommended changes 列に置き換え先の API を挙げてくれることもあるようです。
今回移植するアプリで使っている System.IO.File.GetAccessControl メソッドが Not Supported で代替案もないということが分かります。今回ここは移植後に失敗させるために敢えてそのまま残しておきます。

■ packages.config を PackageReference 形式に変更する

プロジェクトの中に packages.config ファイルがある場合、ソリューションエクスプローラで packages.config ファイルを 右クリック > packages.config を PackageReference に移行する を選択します。

ポップアップで OK を選択します。

f:id:rksoftware:20190615123538j:plain

これで packages.config ファイルがなくなり、パッケージの情報が PackageReference 形式で .csproj ファイルに追加されます。

■ .csproj を SDK スタイルに書き換える

ここの手順が複雑です。ここでプロジェクトをいったんアンロードします。アンロードすると .csproj ファイルが編集できるようになります。ソリューションエクスプローラで プロジェクト名を 右クリック > プロジェクトのアンロード を選択します。

プロジェクトがアンロード済みになったらプロジェクト名を 右クリック > 編集 (プロジェクト名).csproj を選択します。.csproj ファイルがエディタで開きます。ここで開いたファイルを今後【α1】と呼びます。

ここで、一番間違えやすい手順が出てきます。
開いた .csproj ファイルの内容をコピーして別のファイルに保存しておきます。大事な手順ですので忘れずに行ってください。ここで保存した内容を今後【β1】と呼びます。

.csproj を SDK スタイルにする

エディタで開いている .csproj の内容をすべて消して、次のコードを貼り付けます。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
</Project>

続けて、インストールパッケージやプロジェクトの参照の設定を戻します。【β1】から次の様な記述を探し、【α1】に追加します。

  <ItemGroup>
    <PackageReference Include="EntityFramework">
      <Version>6.2.0</Version>
    </PackageReference>
    <PackageReference Include="Newtonsoft.Json">
      <Version>12.0.2</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj">
      <Project>{ca473fbd-a849-4b86-a427-5cbc9101c5c8}</Project>
      <Name>ClassLibrary1</Name>
    </ProjectReference>
  </ItemGroup>

ここで、【β1】の中の <ProjectReference 要素の子要素の <Project および <Name 要素は SDK スタイルでは不要なので削除します。削除後は次のようになります。

  <ItemGroup>
    <PackageReference Include="EntityFramework">
      <Version>6.2.0</Version>
    </PackageReference>
    <PackageReference Include="Newtonsoft.Json">
      <Version>12.0.2</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
  </ItemGroup>

SDK スタイルへの移行完了

ここまでの手順で 【α1】は次のようになりました。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="EntityFramework">
      <Version>6.2.0</Version>
    </PackageReference>
    <PackageReference Include="Newtonsoft.Json">
      <Version>12.0.2</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
  </ItemGroup>
</Project>

アンロードしていたプロジェクトをロードします。ソリューションエクスプローラでプロジェクト名を 右クリック > プロジェクトの再読み込み を選択します。

■ ターゲットフレームワークを .NET Core 3.0 に変更する

ここまでの手順で、.NET Core 3.0 へ移行する準備は整いましたが、実はまだ .NET Framework で動作しています。実際ビルドも実行もできます。.NET Portability Analyzer で .NET Core 3.0 で使えないとされた System.IO.File.GetAccessControl メソッドも元気に動作します。

.NET Core 3.0 に変更するためにまた【α1】を編集します。SDK スタイルだとプロジェクトをアンロードしなくても編集ができるようになります。
ソリューションエクスプローラでプロジェクト名を 右クリック > プロジェクト ファイルの編集 を選択します。

選択するメニュー項目の文言が違うことに気を付けてください。 編集 (プロジェクト名).csproj という文言を探しても見つかりません! 注意!!

【α1】の中に <TargetFramework>net472</TargetFramework> という箇所があります、ここで .NET Framework のプロジェクトであると指定されていますので、これを .NET Core 3.0 向けと書き換えるだけで OK です。

net472netcoreapp3.0 に書き換えるだけの簡単なお仕事です。変更後は次の様になります。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="EntityFramework">
      <Version>6.2.0</Version>
    </PackageReference>
    <PackageReference Include="Newtonsoft.Json">
      <Version>12.0.2</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
  </ItemGroup>
</Project>

■ .NET Core 3.0 でビルド

簡単なお仕事で .NET Core 3.0 に移植できたので、ビルドしてみます。

・・
・・・

エラー    CS0117  'File' に 'GetAccessControl' の定義がありません

みごと! コンパイルエラーがでます!! 大丈夫、計画通りです。

■ 使えないアイツを削除

.NET Portability Analyzer で Not Supported とされていた System.IO.File.GetAccessControl メソッドが本当に使えないことを確認するために残していたことを覚えているでしょうか?
まさに狙い通り、'File' に 'GetAccessControl' がないと怒られました。完璧です。
ということで、該当コードを削除します。

using System.Windows;

public partial class MainWindow : Window
{
    public MainWindow() { InitializeComponent(); }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //var ac = System.IO.File.GetAccessControl(@"nanika fairuno pasu");
        //MessageBox.Show(ac.ToString());
    }
}

これでビルドも通って無事実行できました。

■ Microsoft.Windows.Compatibility

ビルドは通り実行できましたが、機能を減らしてしまいました。こんな時は Microsoft.Windows.Compatibility パッケージに命を救われることがあるそうです。NuGet からインストールしてください。このパッケージは、.NET Framework に有った 21K の API を追加してくれるそうです。
インストールしてみると System.IO.File.GetAccessControl メソッドはありませんが

static FileSecurity System.IO.FileSystemAclExtensions.GetAccessControl(this FileInfo fileInfo)

拡張メソッドが含まれています。これを使って先ほど削除した機能を復活させましょう。ちなみにこのメソッドは .NET Framework に存在しているメソッドです。

using using System.IO; の追加が必要な点に気を付けてください。

using System.Windows;
using System.IO;

public partial class MainWindow : Window
{
    public MainWindow() { InitializeComponent(); }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var ac = new System.IO.FileInfo(@"nanika fairuno pasu").GetAccessControl();
        MessageBox.Show(ac.ToString());
    }
}

これでビルドも通って機能も削除せず無事実行できました。デスクトップアプリの .NET Core 3.0 移植、完全に理解しました。