Ожидаемый релиз netcore 3.0 позволяет запускать wpf на netcore. Процедура перевода для одного несложного проекта занимает один-два дня. Каждый последующий — много быстрее.
![](https://habrastorage.org/webt/kd/zc/mi/kdzcmitlmo3pixuo3lm1aimlv4e.png)
Подготовка и конвертация проектов
Первый этап подготовки — установить и запустить Portability Analyzer. На выходе получим Excel табличку, в которой увидим на сколько наш код соответствует новым требованиям.
![](https://habrastorage.org/webt/by/nm/5y/bynm5y9-v4tpcne2wzrmpr0mfr0.png)
Процедуру конвертации старых проектов провернули в несколько этапов.
- Microsoft рекомендует поднять для старых проектов версию фреймворка до .Net Framework 4.7.3.
- Сконвертировать структуру старых проектов в новый формат. Заменить packages.config на PackageReference.
- В-третьих, скорректировать структуру файла csproj в формат netcore.
Хочу поблагодарить Янгирова Эмиля с его докладом по миграции на netcore, который очень пригодился. Ссылка на его доклад.
Получилось, что первый этап мы решили пропустить. Второй этап подразумевал необходимость конвертации больше 100 проектов. Как осуществляется этот процесс можно подробно прочитать здесь.
Поняли, что без автоматизации никак не обойтись. Воспользовались уже готовым решением: CsprojToVs2017. Пусть название проекта вас не смущает: утилита конвертит и для Visual Studio 2019.
Что произойдёт?
Уменьшится размер файлов csproj. За счет чего? Из csproj уйдут все подключенные файлы с исходным кодом, уберутся лишние строки и т.п.
- <Compile Include="Models\ViewModels\HistoryViewModel.cs" />
- <Compile Include="Properties\Settings.Designer.cs">
- <AutoGen>True</AutoGen>
- <DependentUpon>Settings.settings</DependentUpon>
- <DesignTimeSharedInput>True</DesignTimeSharedInput>
- </Compile>
Сократятся записи подключенных библиотек и подпроектов.
- <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
- <HintPath>..\packages\NLog.4.3.3\lib\net45\NLog.dll</HintPath>
- <Private>False</Private>
- </Reference>
- <ProjectReference Include="..\WpfCommon\WpfCommon.csproj">
- <Project>{7ce118f6-2978-42a7-9e6a-ee5cd3057e29}</Project>
- <Name>WpfCommon</Name>
- </ProjectReference>
+ <PackageReference Include="NLog" Version="4.6.7" />
+ <ProjectReference Include="..\WpfCommon\WpfCommon.csproj" />
Общие настройки для нескольких проектов можно унести в Directory.BuildProps. Это такой специальный файл, в который заглядывает MsBuild.
По аналогии с .gitignore и .editorconfig у нас есть глобальный файл с общими настройками.
Частные настройки PropertyGroup для подкаталогов/проектов добавляем в конкретные csproj файлы. Подробно можно почитать здесь.
Зависимости
Старые зависимости будут для netframework. Придется найти альтернативу или похожие пакеты для nuget. Для многих проектов уже есть Nuget-пакет, которые поддерживают netcore или netstandard.
К примеру, в проекте использовалась старая версия DI Unity. При переходе на новую версию пришлось обновить using и поправить код в двух-трёх местах.
using Microsoft.Practices.Unity -> using Unity;
А возможно будет достаточно апнуть все версии пакетов. И на всякий случай перезапустить студию.
Изменить csproj на использование netcore
В проектах, которые используют WPF контролы, нужно изменить формат на Microsoft.NET.Sdk.WindowsDesktop:
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup/>
+<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ <AssemblyTitle>MyEnterpriseLibrary</AssemblyTitle>
+ <Product>MyEnterpriseLibrary</Product>
+ <OutputPath>..\bin\$(Configuration)\</OutputPath>
+ <UseWPF>true</UseWPF>
+ <!--Если уже есть файл assemblyinfo и он вас устраивает, то следует добавить -->
+ <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</Project>
Для ClassLibrary достаточно оставить тип Microsoft.NET.Sdk:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<AssemblyTitle>MyEnterpriseLibrary</AssemblyTitle>
<Product>MyEnterpriseLibrary</Product>
<OutputPath>..\bin\$(Configuration)\</OutputPath>
</PropertyGroup>
<!-- ... -->
</Project>
Возможно, в некоторых проектах, которые используются контролы Windows Forms придётся ещё и воткнуть обращение к UseWindowsForms:
<UseWindowsForms>true</UseWindowsForms>
В csproj изменился подход к потоку компиляции ресурсов. Раньше формат позволял подключить файл и к ресурсам, и к Content,
и хоть куда.
Теперь, если файл попал в какую-то коллекцию, то его нужно из неё вытащить, а уже потом включить в нужную группу.
Вот код, который вытаскивает file.json из коллекции None и подключает его к коллекции Resource.
<ItemGroup>
<None Exclude="file.json" />
<Resource Include="file.json" />
</ItemGroup>
Соответственно, все файлы, которые не являются исходниками, надо вытащить из коллекции None и подключить к ресурсам. Например, так:
<ItemGroup Condition="'$(UseWPF)' == 'true' And $(UseWindowsForms) != 'true'">
<None Exclude="**\*.xml;**\*.xsl;**\*.xslt;**\*.txt;**\*.bmp;**\*.ico;**\*.cur;**\*.gif;**\*.jpeg;**\*.jpe;**\*.jpg;**\*.png;**\*.dib;**\*.tiff;**\*.tif;**\*.inf;**\*.compositefont;**\*.otf;**\*.ttf;**\*.ttc;**\*.tte" />
<Resource Include="**\*.xml;**\*.xsl;**\*.xslt;**\*.txt;**\*.bmp;**\*.ico;**\*.cur;**\*.gif;**\*.jpeg;**\*.jpe;**\*.jpg;**\*.png;**\*.dib;**\*.tiff;**\*.tif;**\*.inf;**\*.compositefont;**\*.otf;**\*.ttf;**\*.ttc;**\*.tte" />
</ItemGroup>
Некоторые строки придётся удалить, так как сбивают версию фреймворка на .net framework 4.0.
Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"
Кое-где после конвертации останутся странные записи, которые не дают проекту компилироваться. Вот примеры таких конструкций:
- <ItemGroup>
- <EmbeddedResource Include="**\*.resx" />
- </ItemGroup>
- <Compile Remove="something.cs">
Клиенты WCF
Если использовался WCF, то придётся перегенерировать привязки. Как это сделать правильно можно прочитать тут: docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework#updating-wcf-client-usage
Что не взлетит?
Stylecop и анализ кода.
В части наших проектов использовались статические анализаторы кода. При переходе на современные редакции MsBuild сборщик явно предлагает использовать вместо старых статических анализаторов кода новые Roslyn-анализаторы.
Пришлось перевести старые правила на использование Nuget-пакетов Stylecop.Analyzers и FxCop.Analyzers следуя этому руководству Microsoft..
Если у вас несколько проектов в разных папках (монорепозиторий), то гораздо удобнее вынести подключение анализаторов в Build.props и настраивать едиными ruleset.
Вот что получилось:
- <RunCodeAnalysis>true</RunCodeAnalysis>
+ <PackageReference Include="FxCop.Analyzers" Version="2.9.4" />
Файлы — сироты
Старый формат csproj подразумевал явное подключение .cs файлов. При этом иногда при переименованиях или рефакторингах старые файлы удалялись из csproj, но не удалялись явно с файловой системы. В новом формате csproj автоматически подхватятся все файлы, которые находятся в папке с проектом, как раз те самые, которые не были удалены ранее. Скорее всего в них будут ошибки, обращения к уже несуществующим классам, методам, ресурсам. Выльется в банальные ошибки при сборке.
Ресурсы
В одном из проектов использовались SplashScreen, один из которых случайно выбирался при запуске. Экземпляру SplashScreen при инициализации скармливался путь к ресурсу. Почему-то на netcore 3 победить не удалось: ругается на отсутствие ресурса.
Код, который вроде работает
Код, который работал в .Net Framework, с большой вероятностью заработает и в netcore. Но могут быть участки кода, на которые компилятор закрыл глаза. В этом случае, если код доберется до инструкций, которые не реализованы в netcore, мы словим PlatformException.
Для того, чтобы поискать такие места, есть специальный анализатор: github.com/dotnet/platform-compat .
Зачем всё это, если проект работает?
Преимуществ не так много, но тем не менее, они есть.
- Ваш код получит все оптимизации, добавленные в netcore.
- Увеличится скорость запуска приложения.
- Нацеливание на будущие версии C#.
- Уменьшится время сборки проектов благодаря новым версиям csproj.
- Упаковка в единственный exe.
Microsoft не подталкивает к переводу приложений на новые рельсы. Тем не менее, если ваше приложение является плагином другого бОльшего, то есть смысл нацелиться на будущие релизы, которые могут быть и на netcore.
Полезные ссылки
- docs.microsoft.com/ru-ru/dotnet/desktop-wpf/migration/convert-project-from-net-framework
- docs.microsoft.com/ru-ru/dotnet/core/porting/winforms
- devblogs.microsoft.com/dotnet/migrating-a-sample-wpf-app-to-net-core-3-part-1
- devblogs.microsoft.com/dotnet/migrating-a-sample-wpf-app-to-net-core-3-part-2
- devblogs.microsoft.com/dotnet/how-to-port-desktop-applications-to-net-core-3-0
- www.nuget.org/packages/Microsoft.Windows.Compatibility
- www.youtube.com/watch?v=jLBUk1hvLoU