My blog about application development on the .NET platform and Windows®.

WPF on .NET Core

Microsoft announced at Build 2018 back in May that they are bringing .NET Core to the Windows desktop applications frameworks, including both WPF and Windows Forms. This means that your client applications will be able to take advantage of the various performance improvements that have been introduced in .NET Core and that you will be able to deploy them as self-contained executables (.exes) that have no dependency upon any pre-installed version of .NET.

This article provides an up-to-speed introduction to the new project format that .NET Core uses and how you can use the command-line interface (CLI) to create, build and deploy both self-contained and framework-dependent desktop applications that run on .NET Core. You have been able to this for a while using a pre-release version of the SDK, but since the official preview versions of both .NET Core 3 and Visual Studio 2019 were announced last week it’s about time to familiarize yourself with the biggest modernization WPF and Windows Forms have seen in years.

.NET Core

As you are probably already very well aware of, .NET Core is Microsoft’s open source and cross-platform version of .NET where they focus most of their development efforts these days. They have made some significant performance improvements to both the runtime and the core libraries as well as introduced a bunch of new types that make it easier to develop cutting edge applications that runs across all major operating systems. The latest stable version is 2.2 and it was also released last week.

.NET Framework

The .NET Framework is the classic Windows-only version of .NET that has been around since 2002 and is installed on over a billion PC devices. All updates to the .NET Framework since version 4.0 have been in-place updates. This means that when installing an update there is always a risk that you will break an existing application in one way or another. Even if an existing application that targets an earlier version of the framework is not actually recompiled against the new framework version, it’s still affected by the fact that the in-place update modifies the code of some of the assemblies that are part of the framework. This well-known combability issue is apparently one of the main reasons why Microsoft has chosen and continues to choose not to port most of the features and improvements they have introduced in .NET Core back to the .NET Framework.

With the side-by-side support that comes with .NET Core, you can install a new application that uses the latest features of .NET without taking the risk of breaking an existing application. The reliability and stability concerns regarding the “full” desktop version of the .NET Framework (currently in version 4.7.2) effectively means that it will enter a “hotfixes-only” mode. It will still be fully supported for years to come as it is a vital part of Windows but if you want to take advantage of the latest features of .NET in the future, you will have to target .NET Core sooner or later.

.NET Core 1.0 – 2.2

So far only ASP.NET websites, services, and console applications have been able to target .NET Core but starting with .NET Core version 3.0, it also applies to WPF, Windows Forms and Universal Windows Platform (UWP) apps. You may ask yourself whether this means that you will be able to run a WPF application on Linux or any other platform than Windows? I am afraid the answer to that question is no. While .NET core itself is and will continue to be an open-source framework for building high-performance applications that run across all major platforms, the desktop frameworks are still specific to Windows only. They will be delivered as desktop packs layered on top of .NET Core, similar to the existing Windows Compatibility Pack that is a logical extension of .NET Standard 2.0 and provides access to Windows-specific APIs.

Command-Line interface (CLI)

If you download and install the preview version of .NET Core 3 from Microsoft’s official website, or a pre-release version of the .NET Core SDK for Windows that contains the very latest bits from GitHub, you can start creating client applications that targets .NET Core today. Using the CLI to issue the following command from a developer command prompt creates a new WPF project based on a default project template:

>dotnet new wpf --output WpfCoreApp1

A corresponding template for Windows Forms projects have also been added to the template engine. You can list all available templates using the dotnet new -all command.

Csproj Format

The --output (or -o for short) option specifies the location where the project files are generated. If you examine the files in this folder after running the command, they should look familiar. App.xaml and MainWindow.xaml look very much like those created by Visual Studio when you create a new WPF application project that targets the .NET Framework. If you look at the project file (.csproj), you will see that it has a new lean and clean format compared to .NET Framework projects:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>

With the new csproj format that the .NET Core SDK uses, project files are much easier to understand and modifying manually without using Visual Studio. You no longer need to explicitly reference and include files in a project. Source files and resources that are in the same folder as the .csproj file itself are implicitly included in the project, provided that they match a pattern that are defined in the specific SDK specified by the new mandatory Sdk attribute of the Project element on the first line in the project file.

SDK

An SDK, or a shared SDK component as it’s called in the official documentation, is a set of MSBuild tasks and targets that knows how to build .NET Core code. Previous versions of the .NET Core tools included Microsoft.NET.Sdk.Web for building and publishing web applications, Microsoft.NET.Sdk.Razor for building ASP.NET MVC razor views and layouts, and Microsoft.NET.Sdk for building non-web based apps.

Microsoft.NET.Sdk.WindowsDesktop is a new SDK that includes the desktop pack that contains the GUI framework specific assemblies, such as for example PresentationCore.dll and System.Windows.Forms.dll. It’s installed into C:\Program Files\dotnet\sdk\{version}\Sdks\Microsoft.NET.Sdk.WindowsDesktop where {version} is the currently installed version of the .NET Core SDK on your development machine. You can display the current version in use using the dotnet --version command.

The inner workings of MSBuild is out of scope this article but if you open up the Sdk/Sdk.targets file in a text editor, you’ll see that the Microsoft.NET.Sdk.WindowsDesktop SDK imports Microsoft.NET.Sdk to bring in the .NET Core stuff, and that the Windows desktop specific build details are defined in the files in the targets folder. In targets/Microsoft.NET.Sdk.WindowsDesktop.props, the glob pattern mentioned earlier that includes all XAML files in the project is defined. This file also uses the value of the element in the project file to decide what assemblies to reference when building the project and providing tooling support for it in Visual Studio. There is a corresponding element for Windows Forms projects and you can use both of them in the same project file if your application uses both frameworks.

Of course, you don’t really need to care about these details when developing and building your applications. If you open the project in Visual Studio, you will be able to add breakpoints, build and debug your code just like you are used to without having to know anything about the details of the how the app is being built and handled for you by the tooling behind the scenes. If you look at the project references and expand the SDK node in the solution explorer, you will see that it consists of Microsoft.NETCore.App and Microsoft.WindowsDesktopApp. The former SDK includes all core assemblies that are part of .NET Core and the latter includes all Windows specific assemblies that’s included in the desktop pack. There is a single desktop SDK used for both WPF and Windows Forms by the way. Visual Studio 2017 does not officially support .NET Core 3 projects, but Visual Studio 2019 will. The first preview version is available for download here.

Package References

Another thing that should be mentioned regarding the new project format is that there is no longer any need for a separate packages.config file to keep track of NuGet dependencies. When you add a NuGet package to a traditional .NET Framework project, there is a element added to the project file for each dependency of the installed package. If you for example install a single NuGet package and this package has a dependency upon three other packages, you get a total of four elements added. Besides, the package information is duplicated in the packages.config file.

In .NET Core projects, there is only a single transitive element for the actual NuGet package that you installed added to the .csproj file, e.g.:

<ItemGroup>
  <PackageReference Include="newtonsoft.json" Version="12.0.1" />
</ItemGroup>

Any dependencies will be included automatically when building. You can see a package’s dependencies in Visual Studio by expanding the Dependencies->NuGet tree in the solution explorer.

Multi-Targeting

The new project system also let you use same project file to target multiple .NET runtimes. This is a more common thing to do when developing libraries, but it can certainly be done with executable applications as well. If you for example want your WPF application to target both .NET Core and the .NET Framework, you can simply rename the <TargetFramework> element in the project file to <TargetFrameworks> (with an additional “s” at the end) and specify a semi-colon separated list of all the frameworks that you want to target, e.g.:

<TargetFrameworks>netcoreapp3.0;net452</TargetFrameworks>

A framework is identified by a target framework moniker (TFM). net452 is the TFM for the .NET Framework 4.5.2. All supported frameworks and their monikers are listed on MSDN.

If you build the application using the dotnet build command, or using Visual Studio, you will get an output folder created for each target framework that you have specified in the project file. In this case the compiled app in bin\Debug\netcoreapp3.0 will run on .NET Core whereas the one in bin\Debug\net452 will run on the .NET Framework.

Deployment

When it comes to application deployment, .NET Core supports two different modes; framework-dependent and self-contained. The self-contained approach bundles your application-specific code together with the .NET Core runtime and libraries to create an app that basically have no dependency on the runtime environment. You must specify the target operating system (OS) when you build such a self-contained app. The target OS is specified using a runtime identifier (RID) and you can either include this one inside a element in the project file, or specify it using the --runtime (-r) option when you publish the app using the CLI:

> dotnet publish --runtime win-x64 --framework netcoreapp3 --configuration release --output bin/self-contained/

For WPF and Windows Forms applications, you would use either win-x86 or win-x64 or any other version-specific Windows RID. Note that you also must specify a target framework using the --framework (-f) option if you use the <TargetFrameworks> element to target multiple frameworks in your project file.

A framework-dependent application is created using the same publish command. If you have included a <RuntimeIdentifier> element in the project file, you would use the --self-contained option with a value of false to deploy only your application and any third-party dependencies it may have. Otherwise you may just leave out the --runtime (-r) option.

Version Roll-Forward

When a framework-dependent application is run, it will use the latest patch version of .NET Core that is present on the target machine. This means that if the app was built against the target framework netcoreapp3 (version 3.0.0) on the development machine or build agent and the latest installed .NET runtime on the target machine is version 3.0.1, the app will run using the latter runtime version. If no 3.0.* versions are installed, the highest 3.* version will be used.

For self-contained deployments, the runtime version selection occurs when you publish the app and not when you run it. The publishing process selects and bundles the app with the latest patch version by default. The roll-forward policies that select the actual runtime version for a .NET Core application are explained in detail on MSDN.

The major advantage with framework-dependent deployments is that the size of the app will be smaller as it doesn’t include .NET Core itself. If you compare the contents of the output folder between a self-contained and a framework-dependent published application, you will see that the former contains a lot more assemblies.

Portability

Besides the changes to the project file and the tooling, you also need to make sure that you are not using any API that hasn’t been ported over to .NET Core before you decide to upgrade an existing .NET Framework application to target the new runtime. There is a portability analyzer tool available for download that helps you do this. It’s a simple Windows Forms application that lets you select a folder that contains some assemblies to analyze. It produces an Excel file (.xlsx) that tells you which types and members used in these assemblies that are not supported in .NET Core. Although many of the most common .NET Framework APIs have been ported over, there are still some missing. You will have to replace these in your code to be able to upgrade to .NET Core.

It won’t be long before the final versions of .NET Core 3 and Visual Studio 2019 are released and to quote the official .NET blog, it’s truly an exciting time to be a .NET developer these days. Don’t you agree?



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s