Porting WPF applications to netcore 3.0

The upcoming release of netcore 3.0 allows you to run wpf on netcore. The translation procedure for one simple project takes one to two days. Each subsequent one is much faster.

















Preparation and conversion of projects





The first step in the preparation is to install and run Portability Analyzer. At the output, we get an Excel plate in which we will see how much our code meets the new requirements.













The procedure for converting old projects was turned in several stages.







  1. Microsoft recommends upgrading the framework version to .Net Framework 4.7.3 for older projects.
  2. Convert the structure of old projects to a new format. Replace packages.config with PackageReference.
  3. Thirdly, adjust the structure of the csproj file to netcore format.




I want to thank Emil Yangirov with his report on migration to netcore, which was very useful. Link to his report.









It turned out that we decided to skip the first stage. The second stage implied the need to convert more than 100 projects. How this process is carried out can be read in detail here .







We understood that automation could not be avoided. Used the ready-made solution: CsprojToVs2017 . Let the name of the project not bother you: the utility also converts for Visual Studio 2019.









What will happen?



The size of csproj files will decrease. Due to what? All connected files with source code will leave csproj, extra lines will be removed, etc.









- <Compile Include="Models\ViewModels\HistoryViewModel.cs" /> - <Compile Include="Properties\Settings.Designer.cs"> - <AutoGen>True</AutoGen> - <DependentUpon>Settings.settings</DependentUpon> - <DesignTimeSharedInput>True</DesignTimeSharedInput> - </Compile>
      
      







Connected library and subproject entries will be reduced.









 - <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" />
      
      







The general settings for several projects can be carried out in Directory.BuildProps. This is such a special file that MsBuild looks into.

By analogy with .gitignore and .editorconfig, we have a global file with general settings.

We add the private PropertyGroup settings for subdirectories / projects to specific csproj files. Details can be found here.









Dependencies





Old dependencies will be for netframework. You will have to find an alternative or similar packages for nuget. For many projects, there is already a Nuget package that supports netcore or netstandard.









For example, the project used an old version of DI Unity. When switching to a new version, I had to update using and fix the code in two or three places.







 using Microsoft.Practices.Unity -> using Unity;
      
      







And it may be enough to upgrade all versions of packages. And just in case, restart the studio.









Change csproj to use netcore





In projects that use WPF controls, you need to change the format to 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>
      
      







For ClassLibrary, just leave the Microsoft.NET.Sdk type:









 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <AssemblyTitle>MyEnterpriseLibrary</AssemblyTitle> <Product>MyEnterpriseLibrary</Product> <OutputPath>..\bin\$(Configuration)\</OutputPath> </PropertyGroup> <!-- ... --> </Project>
      
      







Perhaps, in some projects that use the Windows Forms controls, you will also have to stick a call to UseWindowsForms:







 <UseWindowsForms>true</UseWindowsForms>
      
      







Csproj has changed its approach to resource compilation flow. Previously, the format allowed you to connect the file to both resources and Content,

and even where.







Now, if the file is in some kind of collection, then you need to remove it from it, and only then include it in the desired group.

Here is the code that pulls file.json from the None collection and connects it to the Resource collection.









 <ItemGroup> <None Exclude="file.json" /> <Resource Include="file.json" /> </ItemGroup>
      
      







Accordingly, all files that are not source must be removed from the None collection and connected to the resources. For example, like this:









 <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>
      
      







Some lines will have to be deleted, since they knock down the framework version on .net framework 4.0.









  Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"
      
      







In some places after the conversion, strange entries will remain that prevent the project from compiling. Here are examples of such constructions:









 - <ItemGroup> - <EmbeddedResource Include="**\*.resx" /> - </ItemGroup> - <Compile Remove="something.cs">
      
      







WCF Clients





If you used WCF, you will have to regenerate the bindings. How to do this correctly can be read here: docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework#updating-wcf-client-usage









What won't take off?





Stylecop and code analysis.





Some of our projects used static code analyzers. When migrating to modern MsBuild editions, the collector explicitly suggests using the new Roslyn analyzers instead of the old static code analyzers.









I had to translate the old rules to use the Nuget packages Stylecop.Analyzers and FxCop.Analyzers following this guide from Microsoft. .

If you have several projects in different folders (monorepository), then it is much more convenient to make the connection of analyzers in Build.props and configure them as a single ruleset.









Here's what happened:









 - <RunCodeAnalysis>true</RunCodeAnalysis> + <PackageReference Include="FxCop.Analyzers" Version="2.9.4" />
      
      







Files - Orphans





The old csproj format implied explicit connection of .cs files. At the same time, sometimes when renaming or refactoring old files were deleted from csproj, but they were not deleted explicitly from the file system. In the new csproj format, all files that are in the project folder will be automatically picked up, just the ones that have not been deleted before. Most likely there will be errors in them, appeals to already non-existent classes, methods, resources. It will result in commonplace errors during assembly.









Resources





One of the projects used SplashScreen, one of which was randomly selected at startup. The SplashScreen instance was initialized with the path to the resource. For some reason, we could not win at netcore 3: swears at the lack of a resource.









Code that seems to work





Code that worked in the .Net Framework is likely to work in netcore as well. But there may be portions of code that the compiler closed his eyes to. In this case, if the code gets to instructions that are not implemented in netcore, we will catch a PlatformException.







In order to search for such places, there is a special analyzer: github.com/dotnet/platform-compat .









Why all this if the project is working?





There are not many advantages, but nevertheless, they are.













Microsoft is not pushing the application to a new footing. Nevertheless, if your application is a plugin of another larger one, then it makes sense to aim at future releases, which may also be on netcore.









useful links








All Articles