How to port desktop applications to .NET Core 3.0

Olia Gavrysh

Olia

In this post, I will describe how to port a desktop application from .NET Framework to .NET Core. I picked a WinForms application as an example. Steps for WPF application are similar and I’ll describe what needs to be done different for WPF as we go. I will also show how you can keep using the WinForms designer in Visual Studio even though it is under development and is not yet available for .NET Core projects.

About the sample

For this post, I’ll be using a Memory-style board game application. It contains a WinForms UI (MatchingGame.exe) and a class library with the game logic (MatchingGame.Logic.dll), both targeting .NET Framework 4.5. You can download the sample here. I’ll be porting the application project to .NET Core 3.0 and the class library to .NET Standard 2.0. Using .NET Standard instead of .NET Core allows me to reuse the game logic to provide the application for other platforms, such as iOS, Android or web.

You can either watch Scott Hunter and me doing the conversion in the following video, or you can follow the step-by-step instructions below. Of course, I won’t be holding it against you, if you were to do both.

Step-by-step process

I suggest doing the migration in a separate branch or, if you’re not using version control, creating a copy of your project so you have a clean state to go back to if necessary.

Before porting the application to .NET Core 3.0, I need to do some preparation first.

Preparing to port

  1. Install .NET Core 3 and Visual Studio 2019 Preview version (Visual Studio 2017 only supports up to .NET Core 2.2).
  2. Start from a working solution. Ensure the solution opens, builds, and runs without any issues.
  3. Update NuGet packages. It’s always a good practice to use the latest versions of NuGet packages before any migration. If your application is referencing any NuGet packages, update them to the latest version. Ensure your application builds successfully. In case of any NuGet errors, downgrade the version and find the latest one that doesn’t break your code.
  4. Run the .NET Portability Analyzer to determine if there are any APIs your application depends on that are missing from .NET Core. If there are, you need to refactor your code to avoid dependencies on APIs, not supported in .NET Core. Sometimes it’s possible to find an alternative API that provides the needed functionality.
  5. Replace packages.config with PackageReference. If your project uses NuGet packages, you need to add the same NuGet packages to the new .NET Core project. .NET Core projects support only PackageReference for adding NuGet packages. To move your NuGet references from packages.config to your project file, in the solution explorer right-click on packages.config -> Migrate packages.config to PackageReference….You can learn more about this migration in the Migrate from packages.config to PackageReference article.

Porting main project

Create new project

  1. Create new application of the same type (Console, WinForms, WPF, Class Library) as the application you are wanting to port, targeting .NET Core 3. At the moment we did the demo Visual Studio templates for desktop projects were under development, so I used the console.
  2. In the project file copy all external references from the old project, for example:
  3. Build. At this point if the packages you’re referencing support only .NET Framework, you will get a NuGet warning. If you have not upgraded to the latest versions of NuGet packages on the step 3, try to find if the latest version supporting .NET Core (.NET Standard) is available and upgrade. If there are no newer version, .NET Framework packages can still be used but you might get run-time errors if those packages have dependencies on APIs not supported in .NET Core. We recommend to let the author of the NuGet package know that you’d be interested in seeing the package being updated to .NET Standard. You can do it via Contact form on the NuGet gallery.

Fast way (replace existing project file)

First, let’s try the fast way to port. Make sure you have a copy of your current .csproj file, you might need to use it in future. Replace your current .csproj file with the .csproj file from the project you created on the step above and add in the top <PropertyGroup>:

Build your app. If you got no errors – congrats, you’ve successfully migrated your project to .NET Core 3. For porting a dependent (UI) project see Porting UI section, for using the Designer, check out Using WinForms Designer for .NET Core projects section.

Slow way (guided porting)

If you got errors (like I did with my app), it means there are more adjustments you need to make. Instead of the fast way described above, here I’ll do one change at a time and give possible fixes for each issue. Steps below would also help to better understand the process of the migration so if the fast way worked for you but you’re curious to learn all “whys”, keep on reading.

  1. Migrate to the SDK-style .csproj file. To move my application to .NET Core, first I need to change my project file to SDK-style format because the old format does not support .NET Core. Besides, the SDK-style format is much leaner and easier to work with.Make sure you have a copy of your current .csproj file. Replace the content of your .csproj file with the following. For WinForms application:

    For WPF application:

    Note that I set <GenerateAssemblyInfo> to false. In the new-style projects AssemblyInfo.cs is generated automatically by default. So if you already have AssemblyInfo.cs file in your project (spoiler alert: you do), you need to disable auto-generation or remove the file.

    Now copy & paste all references from the old version of .csproj file into the new one. For example:

    NuGet package reference

    Project reference

    The project should build successfully since it is just a new way of writing the same thing. If you got any errors, double check your steps.

    There is also a third-party tool CsprojToVs2017 that can perform the conversion for you. But after using it, you still might need to delete some reference by hand, such as:

  2. Move from .NET Framework to .NET Standard or .NET Core. After successfully converting my library to SDK-style format I’m able to retarget it. In my case I want my class library to target .NET Standard instead of .NET Core. That way, it will be accessible from any .NET implementation if I decide to ship the game to other platforms (such as iOS, Android, or Web Assembly). To do so, replace this

    with

    Build your application. You might get some errors if you are using APIs that are not included in .NET Standard. If you did not get any errors with your application, you can skip the next two steps.

  3. Add Windows Compatibility Pack if needed. Some APIs that are not included in .NET Standard are available in Windows Compatibility Pack. If you got errors on the previous step, you can check if Windows Compatibility Pack can help.I got an error “The name ‘Registry’ does not exist in the current context”, so I added the Microsoft.Windows.Compatibility NuGet package to my project. After installation, the error disappeared.
  4. Install API AnalyzerAPI Analyzer, available as the NuGet package Microsoft.DotNet.Analyzers.Compatibility, will prompt you with warnings when you are using deprecated APIs or APIs that are not supported across all platforms (Windows, Linux, macOS). If you added the Compatibility Pack, I recommend adding the API Analyzer to keep track of all of API usages that won’t work across all platforms.At this point, I am done with the class library migration to .NET Standard. If you have multiple projects referencing each other, migrate them “bottom-up” starting with the project that has no dependencies on other projects.In my example I also have a WinForms project MatchingGame.exe, so now I will perform similar steps to migrate that to .NET Core.

Porting the UI

  1. Add .NET Core UI project. Add a new .NET Core 3.0 UI project to the solution. At this moment, the Visual Studio templates for desktop projects are under development, so I just used the dotnet CLI.

    For WPF projects you’d use this:

    After my new WinForms .NET Core project was created, I added it to my solution.

  2. Link projects. First, delete all files from the new project (right now it contains the generic Hello World code). Then, link all files from your existing .NET Framework UI project to the .NET Core 3.0 UI project by adding following to the .csprojfile.

    If you have a WPF application you also need to include .xaml files:

  3. Align default namespace and assembly name. Since you’re linking to designer generated files (for example, Resources.Designer.cs), you generally want to make sure that the .NET Core version of your application uses the same namespace and the same assembly name. Copy the following settings from your .NET Framework project:
  4. Disable AssemblyInfo.cs generation. As I mentioned earlier, in the new-style projects, AssemblyInfo.cs is generated automatically by default. At the same time the AssemblyInfo.cs file from the old WinForms project will be copied to the new project too, because I linked all files **\*.cs in the previous step. That will result in duplication of AssemblyInfo.cs. To avoid it in MatchingGame.Core project file I set GenerateAssemblyInfo to false.
  5. Run new project. Set your new .NET Core project as the StartUp project and run it. Make sure everything works.
  6. Copy or leave linked. Now instead of linking the files, you can actually copy them from the old .NET Framework UI project to the new .NET Core 3.0 UI project. After that, you can get rid of the old project.

Using the WinForms designer for .NET Core projects

As I mentioned above, the WinForms designer for .NET Core projects is not yet available in Visual Studio. However there are two ways to work around it:

  1. You can keep your files linked (by just not performing the previous step) and copy them when the designer support is available. This way, you can modify the files in your old .NET Framework WinForms project using the designer. And the changes will be automatically reflected in the new .NET Core WinForms project — since they’re linked.
  2. You can have two project files in the same directory as your WinForms project: the old .csproj file from the existing .NET Framework project and the new SDK-style .csproj file of the new .NET Core WinForms project. You’ll just have to unload and reload the project with corresponding project file depending on whether you want to use the designer or not.

Summary

In this blog post, I showed you how to port a desktop application containing multiple projects from .NET Framework to .NET Core. In typical cases, just retargeting your projects to .NET Core isn’t enough. I described potential issues you might encounter and ways of addressing them. Also, I demonstrated how you can still use the WinForms designer for your ported apps while it’s not yet available for .NET Core projects.

Olia Gavrysh
Olia Gavrysh

Program Manager, .NET

Follow Olia   

29 Comments
Avatar
Mike Ward 2019-02-26 14:03:06
Couple of porting questions: 1. Is there a new browser control for WPF in .NET Core 3.0 or is the (very) old Win32 control the only option? 2. Does .NET Core 3.0 resolve the "Air Space" issue when using Win32 controls?
Avatar
Ярослав Бугаря 2019-02-26 14:51:08
Will it be possible to use the new SDK-style .csproj files for WPF or WinForms projects targeting full .NET Framework? BTW, your "Migrate to the SDK-style .csproj file" first two code samples are swapped.
Avatar
Eddie de Bear 2019-02-26 15:33:17
Looks awesome. Now if you could only port WCF Server side over as well I might be able to migrate. 
Avatar
Daren May 2019-02-26 15:45:21
A lot of non-trivial apps written for the enterprise "back in the day" used the patterns and practices Enterprise Library and Prism - running the app analyzer I see a number of things not supported in .NET Core - are then any plans to release "official" updated versions of the NuGET packages that are .NET Core .NET Standard 2.0 compliant (I do see another author has submited .NET Core versions but I can imagine some enterprises being reluctant to adopt those).   T:System.Diagnostics.PerformanceCounterInstaller M:System.Diagnostics.PerformanceCounterInstaller.get_CategoryName Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Diagnostics.PerformanceCounterInstaller M:System.Diagnostics.PerformanceCounterInstaller.get_Counters Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Diagnostics.PerformanceCounterInstaller M:System.Diagnostics.PerformanceCounterInstaller.set_CategoryHelp(System.String) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Diagnostics.PerformanceCounterInstaller M:System.Diagnostics.PerformanceCounterInstaller.set_CategoryName(System.String) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Diagnostics.PerformanceCounterInstaller M:System.Diagnostics.PerformanceCounterInstaller.set_CategoryType(System.Diagnostics.PerformanceCounterCategoryType) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer T:System.Configuration.Install.Installer Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer M:System.Configuration.Install.Installer.#ctor Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer M:System.Configuration.Install.Installer.get_Context Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer M:System.Configuration.Install.Installer.get_Installers Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer M:System.Configuration.Install.Installer.Install(System.Collections.IDictionary) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.Installer M:System.Configuration.Install.Installer.Uninstall(System.Collections.IDictionary) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Security.Principal.WindowsImpersonationContext T:System.Security.Principal.WindowsImpersonationContext Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityIdentity T:System.EnterpriseServices.SecurityIdentity Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityIdentity M:System.EnterpriseServices.SecurityIdentity.get_AccountName Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityCallContext T:System.EnterpriseServices.SecurityCallContext Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityCallContext M:System.EnterpriseServices.SecurityCallContext.get_CurrentCall Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityCallContext M:System.EnterpriseServices.SecurityCallContext.get_DirectCaller Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityCallContext M:System.EnterpriseServices.SecurityCallContext.get_IsSecurityEnabled Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.EnterpriseServices.SecurityCallContext M:System.EnterpriseServices.SecurityCallContext.get_OriginalCaller Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueueTransactionType T:System.Messaging.MessageQueueTransactionType Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueue T:System.Messaging.MessageQueue Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueue M:System.Messaging.MessageQueue.#ctor(System.String,System.Boolean,System.Boolean) Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueue M:System.Messaging.MessageQueue.Close Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueue M:System.Messaging.MessageQueue.get_Transactional Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessageQueue M:System.Messaging.MessageQueue.Send(System.Object,System.Messaging.MessageQueueTransactionType) Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Messaging.MessagePriority T:System.Messaging.MessagePriority Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.Configuration.Install.InstallerCollection T:System.Configuration.Install.InstallerCollection Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.Configuration.Install.InstallerCollection M:System.Configuration.Install.InstallerCollection.Add(System.Configuration.Install.Installer) Microsoft.Practices.EnterpriseLibrary.Common Not supported T:System.AppDomain M:System.AppDomain.CreateDomain(System.String,System.Security.Policy.Evidence,System.AppDomainSetup) Microsoft.Practices.Prism.Composition Not supported T:System.AppDomain M:System.AppDomain.get_Evidence Microsoft.Practices.Prism.Composition Not supported T:System.Security.Principal.WindowsIdentity M:System.Security.Principal.WindowsIdentity.Impersonate(System.IntPtr) Microsoft.Practices.EnterpriseLibrary.Logging Not supported T:System.AppDomainSetup M:System.AppDomainSetup.get_ConfigurationFile Microsoft.Practices.EnterpriseLibrary.Common Not supported
Avatar
bit bonk 2019-02-27 00:00:35
“If you have a WPF application you also need to include .xaml files“ It seems that this is not necessary. Just like all the .cs files can be omitted from the project file this is also true for the .xaml files, which makes the project file even leaner.
Mark Adamson
Mark Adamson 2019-02-27 06:01:25
For single projects it's worthwhile doing this manually as described, but if you have many projects to convert, it's much easier with the mentioned convertor: https://github.com/hvanbakel/CsprojToVs2017 which can be used as a console application or customised in your own conversion tool by adding as a nuget package.
Avatar
Jérôme Dubois 2019-02-27 06:27:46
OK, very good. But what for vb.net developpers? It seems there is no templates for VB.net and Winforms/WPF core.3. In the release version of VS 2019? DJ
Justin Lawrence
Justin Lawrence 2019-02-27 10:32:02
Great video! Is the MyBlazorServerSideApp code available anywhere to take a look at? That was a great demo 
Tadeas Lejsek
Tadeas Lejsek 2019-02-28 00:38:24
What would be the suggested approach on porting libraries with WPF user controls?
Avatar
George Danila 2019-02-28 01:18:06
This is all well and good, but what about real world scenarios? - LOB applications built on WPF and WinForms are usually very complex and make use of external libraries that will probably never get updated to run on .NET Core - I really can't justify spending a huge amount of time and money to port our existing WPF solution to .NET Core.  My suggestion is for Microsoft to focus their efforts on bringing a new, cross-platform desktop framework for people to use on their greenfield projects and provide long-term support for WPF and WinForms on the .NET Framework.
Avatar
Gerard Hayden 2019-03-01 02:56:40
I'll be porting an application which currently uses extensive Visual C++/CLI for most of its functionality including UI. I would be interested in seeing an example of something like that being ported (replacing Visual C++/CLI with C# winforms in the process)/
Avatar
Schaff, Stephen 2019-03-01 16:07:13
I apprciate demos on migrating! I would love to see one migrating a WCF SOAP service to .NET Core!  We have tons of those and they are a big block to us adopting .NET Core.
Avatar
Jason Rosenthal 2019-03-07 09:59:42
I support code migration 100%;   Fingers crossed for all my Silverlight  LOB projects.  
Avatar
Adam Berent 2019-03-08 12:29:08
When I first watched this video I got really excited, finally after all these years I can port some of my Windows Apps to Mac & Linux, you know because .net core is cross platform and all.  Then I read that this is for Windows Only, and I cannot express the debts of disappointment I felt.
Avatar
André Ricardo 2019-03-12 03:46:02
I've followed these instructions but keep getting this error in Azure Pipelines (locally builds fine) ##[error]C:\hostedtoolcache\windows\dncs\3.0.100-preview3-010431\x64\sdk\3.0.100-preview3-010431\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.TargetingPackResolution.targets(157,5): Error NETSDK1073: The FrameworkReference 'Microsoft.WindowsDesktop.App' was not recognized https://dev.azure.com/github-andrericardo/AstroGrep/_build/results?buildId=30 Any tips? Also how can I embed the resources in .Net Core desktop app? At the moment the app fails at runtime at the call this.picBrowse.Image = ((System.Drawing.Image)(resources.GetObject("picBrowse.Image")));   System.Resources.MissingManifestResourceException  HResult=0x80131532  Message=Could not find any resources appropriate for the specified culture or the neutral culture.  Make sure "AstroGrep.Windows.Forms.frmMain.resources" was correctly embedded or linked into assembly "WinformsGUI.Core" at compile time, or that all the satellite assemblies required are loadable and fully signed.  Source=System.Private.CoreLib  StackTrace:   at System.Resources.ManifestBasedResourceGroveler.HandleResourceStreamMissing(String fileName)   at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(CultureInfo culture, Dictionary`2 localResourceSets, Boolean tryParents, Boolean createIfNotExists)   at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)   at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture, Boolean wrapUnmanagedMemStream)   at System.Resources.ResourceManager.GetObject(String name)   at AstroGrep.Windows.Forms.frmMain.InitializeComponent() in D:\astrogrep-code\AstroGrep\WinformsGUI\Windows\Forms\frmMain.Designer.cs:line 462
Avatar
David Rickard (USA) 2019-03-28 23:24:49
The recommended way to support Windows Notifications from a desktop app (https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-desktop-apps) uses RegistrationServices/RegistrationClassContext/RegistrationConnectionType from COM, which is not .NET Core 3.0 compatible. Is there a way to have .NET Core 3.0 but still trigger Windows Notifications?
Jernej Ferjancic
Jernej Ferjancic 2019-04-11 05:28:48
Regarding the chapter "Porting the UI".. works ok with winforms, but for WPF I cannot manage it to work (didn't forget xaml files). Is there any example for porting WPF ?
Jernej Ferjancic
Jernej Ferjancic 2019-04-11 07:04:06
Is there any example of porting WPF based on chapter "Porting the UI"? For winforms works ok, but I cannot manage it to work for WPF (with included xaml files)
Avatar
Devel Oppa 2019-04-16 13:48:30
So once ported how do we publish it as a single executable .exe file? Like what is the switch here for that?
Avatar
Max Mustemann 2019-04-23 01:06:33
Hi, how will it be possible to use both ASP.NET Core (Microsoft.NET.Sdk.Web) and WinForms/WPF (Microsoft.NET.Sdk.WindowsDesktop) components in a .NET Core 3.0 project? We currently have a WPF App (.NET Framework) that references another project where we are referencing ASP.NET Core 2.1 components using NuGet packages (like Microsoft.AspNetCore.Server.Kestrel). Edit: OK, found it: We need to use a FrameworkReference element to reference the shared ASP.NET Core runtime, instead of referencing the NuGet packages like in 2.x. Thanks!
Avatar
Antonio Matos 2019-04-25 14:28:50
I'm a little confused.  Could I migrate my WinForms existing app to .Net Core and run it in another platforms ?
Avatar
Bob Hanson 2019-05-17 07:25:46
Will .Net Core 3.0's suppport for winforms allow the hosting of ActiveX controls? If not, it that a consideration for .Net Core 5.0 or will that functionality remain only in .Net 4.8?
Avatar
Jacob Flax 2019-06-08 19:54:16
Can you do the WinForms convertion to .NET Core 3.0 with a Visual Basic Program?