Портирование desktop приложений на .NET Core 3.0
В этой статье мы рассматриваем, как перенести desktop приложение с .NET Framework на .NET Core. Мы выбрали приложение WinForms в качестве примера. Шаги для приложения WPF схожи, и мы опишем, что нужно сделать для WPF по-другому. Мы также покажем, как вы можете продолжать использовать конструктор WinForms в Visual Studio, даже если он находится в стадии разработки и еще не доступен для проектов .NET Core.
О примере:
Автор для портинга использует настольную игру Memory-style. Она юзает интерфейс WinForms (MatchingGame.exe) и библиотеку классов с игровой логикой (MatchingGame.Logic.dll), обе из которых предназначены для .NET Framework 4.5. Вы можете скачать образец здесь. Мы будем переносить проект приложения на .NET Core 3.0 и библиотеку классов на .NET Standard 2.0. Использование .NET Standard вместо .NET Core позволяет мне повторно использовать игровую логику для предоставления приложения для других платформ, таких как iOS, Android или веб.
Мы рекомендуем выполнить миграцию в отдельной ветке или если вы не используете контроль версий, создать копию своего проекта, чтобы у вас был бекап, к которому можно вернуться при необходимости. Прежде чем портировать приложение на .NET Core 3.0, нам нужно сначала подготовиться.
Подготовка к портированию:
- Установите .NET Core 3 и Visual Studio 2019 Preview версии (Visual Studio 2017 поддерживает только до .NET Core 2.2).
- Начните с рабочего решения. Убедитесь, что решение открывается, собирается и запускается без проблем.
- Обновите NuGet пакеты. Всегда рекомендуется использовать последние версии пакетов NuGet перед любой миграцией. Если ваше приложение ссылается на какие-либо пакеты NuGet, обновите их до последней версии. Убедитесь, что ваше приложение успешно собрано. В случае каких-либо ошибок NuGet, понизьте версию и найдите последнюю версию, которая не нарушает ваш код.
- Запустите анализатор портирования.NET, чтобы определить, существуют ли какие-либо API, от которых зависит ваше приложение и которые отсутствуют в .NET Core. Если таковые имеются, вам необходимо провести рефакторинг своего кода, чтобы избежать зависимостей от API, не поддерживаемых в .NET Core. Иногда можно найти альтернативный API, который обеспечивает необходимую функциональность.
- Замените packages.config на PackageReference. Если ваш проект использует пакеты NuGet, вам нужно добавить те же пакеты NuGet в новый проект .NET Core. Проекты .NET Core поддерживают только PackageReference для добавления пакетов NuGet. Чтобы переместить ваши ссылки NuGet из packages.config в файл проекта, в обозревателе решений щелкните правой кнопкой на packages.config -> Migrate packages.config в PackageReference.
Портирование главного проекта:
1. Создаем новое приложение того же типа (Console, WinForms, WPF, Class Library), что и приложение, которое вы готовы портировать, для .NET Core 3. Для создание проекта используем такую консольную команду:
dotnet new winforms -o <path-to-your-solution>\MatchingGame.Core\
2. В файле проекта скопируйте все внешние ссылки из старого проекта, например:
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> 1 <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
3. Build. На этом этапе, если пакеты, на которые вы ссылаетесь, поддерживают только .NET Framework, вы получите предупреждение NuGet. Если вы не обновили до последних версий пакетов NuGet на шаге 3, попробуйте найти, доступна ли последняя версия, поддерживающая .NET Core (.NET Standard), и выполните обновление. Если более новой версии нет, пакеты .NET Framework все еще можно использовать, но вы можете получить ошибки времени выполнения, если эти пакеты имеют зависимости от API, не поддерживаемых в .NET Core. Microsoft рекомендует сообщить автору пакета NuGet, что вас заинтересовало бы обновление пакета до .NET Standard. Вы можете сделать это через контактную форму в галерее NuGet.
Быстрый способ (заменить существующий файл проекта)
Первое, давайте попробуем быстрый способ портирования. Убедитесь, что у вас есть копия вашего текущего файла .csproj, возможно, вам придется использовать его в будущем. Замените ваш текущий файл .csproj файлом .csproj из проекта, который вы создали на шаге выше, и добавьте в верхнюю <PropertyGroup>:
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
Запустите build своего приложения. Если ошибок нет - поздравляю, вы успешно перенесли свой проект в .NET Core 3. Для портирования зависимого (UI) проекта см. Раздел «Портирование пользовательского интерфейса», чтобы узнать, как использовать Designer, ознакомьтесь с разделом "Использование дизайнера WinForms для проектов .NET Core".
Медленный путь (мануальное портирование)
Если вы получили ошибки, это означает, что вам нужно внести дополнительные коррективы. Вместо быстрого способа, описанного выше. Шаги ниже также помогут лучше понять процесс миграции, поэтому, если быстрый способ сработал для вас, но вам интересно узнать все «почему», продолжайте читать :)
1. Миграция SDK. Чтобы переместить мое приложение в .NET Core, сначала мне нужно изменить файл проекта в формате SDK, поскольку старый формат не поддерживает .NET Core. Кроме того, формат в стиле SDK намного проще и с ним легче работать. Убедитесь, что у вас есть копия вашего текущего файла .csproj. Замените содержимое файла .csproj следующим:
Для приложения WinForms:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net472</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>
Для WPF приложения:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net472</TargetFramework>
<UseWPF>true</UseWPF>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>
Обратите внимание,на то что установлено <GenerateAssemblyInfo> в false.
В проектах нового типа AssemblyInfo.cs генерируется автоматически по умолчанию. Вам нужно отключить автогенерацию или удалить файл AssemblyInfo.cs.Теперь скопируйте и вставьте все ссылки из старой версии файла .csproj в новую. Например:
NuGet package reference
<PackageReference Include="Microsoft.Windows.Compatibility" Version="2.0.1" />
Project reference
<ProjectReference Include="..\MatchingGame.Core\MatchingGame.Core.csproj" />
Существует также сторонний инструмент CsprojToVs2017, который может выполнить преобразование автоматически. Но после его использования вам все равно может понадобиться удалить некоторые ссылки вручную, например:
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
2. Переход от .NET Framework к .NET Standard или .NET Core
После успешного преобразования библиотеки, мы можем изменить таргет для нее. В нашем случае мы хотим, чтобы библиотека классов предназначалась для .NET Standard вместо .NET Core. Таким образом, он будет доступен из любой реализации .NET. Для этого нам нужно заменить
<TargetFramework>net472</TargetFramework>
на
<TargetFramework>netstandard2.0</TargetFramework>
Соберите свое приложение. Вы можете получить некоторые ошибки, если вы используете API, которые не включены в .NET Standard. Если вы не получили никаких ошибок с вашим приложением, вы можете пропустить следующие два шага.
3. При необходимости добавьте Windows Compatibility Pack. Некоторые API, которые не включены в .NET Standard, доступны в пакете совместимости Windows. Если вы получили ошибки на предыдущем шаге, вы можете проверить, может ли пакет Windows Compatibility Pack помочь вам в решении этих ошибок
4. Установите API Analyzer. API Analyzer, доступный в виде пакета NuGet под названием Microsoft.DotNet.Analyzers.Compatibility, будет показывать вам предупреждения при использовании устаревших API или API, которые не поддерживаются на всех платформах (Windows, Linux, macOS). Если вы добавили Windows Compatibility Pack, я рекомендую добавить API Analyzer, чтобы отслеживать все случаи использования API, которые не будут работать на всех платформах. На этом мы заканчиваем миграцию библиотеки классов на .NET Standard. Если у вас есть несколько проектов, ссылающихся друг на друга, перенесите их «снизу вверх», начиная с проекта, который не зависит от других проектов. В примере у нас также есть проект WinForms MatchingGame.exe, поэтому теперь мы выполним аналогичные шаги для переноса. что к .NET Core.
Портирование UI
1. Добавте .NET Core UI проект. Добавляем такой проект с помощью dotnet CLI
для WinForms:
dotnet new winforms -o <path-to-your-solution>\MatchingGame.Core\
Для WPF проектов:
dotnet new wpf -o <path-to-your-solution>\MatchingGame.Core\
2. Ссылка на проекты. Сначала удалите все файлы из нового проекта (прямо сейчас он содержит общий код Hello World). Затем свяжите все файлы из существующего проекта пользовательского интерфейса .NET Framework с проектом пользовательского интерфейса .NET Core 3.0, добавив в файл .csprojfile следующую команду.
<ItemGroup>
<Compile Include="..\<Your .NET Framework Project Name>\**\*.cs" />
<EmbeddedResource Include="..\<Your .NET Framework Project Name>\**\*.resx" />
</ItemGroup>
Если у вас есть приложение WPF, вам также необходимо включить файлы .xaml:
<ItemGroup>
<ApplicationDefinition Include="..\WpfApp1\App.xaml" Link="App.xaml">
<Generator>MSBuild:Compile</Generator>
</ApplicationDefinition>
<Compile Include="..\WpfApp1\App.xaml.cs" Link="App.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="..\WpfApp1\MainWindow.xaml" Link="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Compile Include="..\WpfApp1\MainWindow.xaml.cs" Link="MainWindow.xaml.cs" />
</ItemGroup>
3. Выравняйте пространство имен по умолчанию и имя сборки. Поскольку вы ссылаетесь на файлы, созданные дизайнером (например, Resources.Designer.cs), вы, как правило, хотите убедиться, что версия вашего приложения .NET Core использует то же пространство имен и то же имя сборки. Скопируйте следующие параметры из вашего проекта .NET Framework:
<PropertyGroup>
<RootNamespace><!-- (Your default namespace) --></RootNamespace>
<AssemblyName><!-- (Your assembly name) --></AssemblyName>
</PropertyGroup>
4. Отключите генерацию AssemblyInfo.cs
. Как мы уже упоминали ранее, в проектах нового типа AssemblyInfo.cs генерируется автоматически по умолчанию. В то же время файл AssemblyInfo.cs из старого проекта WinForms будет скопирован и в новый проект, потому что мы связали все файлы ** \ *. Cs на предыдущем шаге. Это приведет к дублированию AssemblyInfo.cs. Чтобы избежать этого в файле проекта MatchingGame.Core, мы установили для GenerateAssemblyInfo значение false.
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
5. Запустите новый проект и убедитесь что все работает.
6. Скопируйте или оставьте ссылку. Теперь вместо связывания файлов вы можете скопировать их из старого проекта пользовательского интерфейса .NET Framework в новый проект пользовательского интерфейса .NET Core 3.0. После этого вы можете удалить старый проект.
Использование дизайнера WinForms для проектов .NET Core
Как мы уже упоминали выше, дизайнер WinForms для проектов .NET Core еще не доступен в Visual Studio. Однако есть два способа обойти это:
- Вы можете сохранять свои файлы связанными (просто не выполняя предыдущий шаг) и копировать их, пока не будет доступна поддержка дизайнера. Таким образом, вы можете изменить файлы в вашем старом проекте .NET Framework WinForms, используя конструктор. И изменения будут автоматически отражены в новом проекте .NET Core WinForms - поскольку они связаны между собой.
- В одном каталоге с вашим проектом WinForms может быть два файла проекта: старый файл .csproj из существующего проекта .NET Framework и новый файл .csproj в стиле SDK нового проекта .NET Core WinForms. Вам просто нужно выгрузить и перезагрузить проект с соответствующим файлом проекта в зависимости от того, хотите ли вы использовать дизайнер или нет.
Итог:
В этом посте мы рассмотрели, как перенести desktop приложение, содержащее несколько проектов, из .NET Framework в .NET Core. В типичных случаях недостаточно просто перенастроить свои проекты на .NET Core. Мы описали потенциальные проблемы, с которыми вы можете столкнуться и способы их решения. Кроме того, мы продемонстрировали, как вы все еще можете использовать дизайнер WinForms для своих портированных приложений, пока он еще не доступен для проектов .NET Core.