Sunday, December 22, 2019

[ADVENT 2019] F# Advent 2019: Revisiting Windows Forms and WPF in F# on .NET Core 3.1

Hi my blog readers!

Now it's time for F# Advent 2019! This annual advent showcases F# bloggers and their articles, in December, thanks to Sergey Tihon, a fellow MS MVP. This year, the complete list of F# Advent 2019 is available at: https://sergeytihon.com/2019/11/05/f-advent-calendar-in-english-2019/

I had submitted schedule for 22nd December by replying to his tweet: https://twitter.com/sergey_tihon/status/1191558104506343425

So let me bring you F# on top of .NET Core 3.1, with code that runs Windows Forms (a.k.a. WinForms) and WPF.

Is this possible? The simple answer is YES. Although there's no new template to create WinForms and WPF in "dotnet new".

The list of available templates are shown when we execute "dotnet new -h":


Yes, it's only available for C#:


So how to have these WPF and WinForms using F#?

It is a bit tricky, but thanks to .NET Core 3.0's new ways of referencing "contextual" technology references, we can leverage WPF and WinForms.

Before .NET Core 3.0

Before .NET Core 3.0, we have to use metapackages combined with specific SDK header in the csproj/fsproj/vbproj, and this metapackage combination with SDK are somehow hard to understand.

It is not so obvious that the SDK and the metapackage are not well integrated, because you still have to specify some of the .NET Core libraries to use as nuget package references. This is quite common in ASP.NET Core templates before 3.0.

Let's use the .NET Core 2.2 latest SDK, 2.2.207 to illustrate this.
To ensure that I'm using at least SDK 2.2.207, I use global.json on the solution/project folder to enforce .NET Core to use the SDK version I want, not choosing the latest release of SDK available.

I had created a global.json file with SDK version, by using the "dotnet new global.json --sdk-version 2.2.207" on command prompt.

This is the content of the global.json:

{
  "sdk": {
    "version": "2.2.207"
  }
}

We can test this by running "dotnet --version", and it will give you the version of the SDK as set in the global.json file.

To create an ASP.NET Core MVC web app in .NET Core 2.2, we use this template when creating using "dotnet new". Now let's create new F# ASP.NET Core MVC and named is as FSharpMVC:

dotnet new mvc -n FSharpMVC -lang F#

This is the content  of the FSharpMVC.fsproj file:



And there is a nuget package reference to "Microsoft.AspNetCore.App", and this is a sample of a metapackage.

Also note the use of SDK in the <Project> header:
Sdk="Microsoft.NET.Sdk.Web"

This means that this is generated using ASP.NET Core template. Unfortunately, not all the ASP..NET Core libraries are included in that metapackage. You still have to reference them as nuget package references, and at many times this may get messy.

For example: references to Microsoft.Extensions.Configuration.* are all have to be referenced as nuget packages explicitly, Also before .NET Core 3.0, there's no support for WinForms and WPF.

After .NET Core 3.0

Now we have WinForms and WPF support as port from .NET Framework to .NET Core in .NET Core since 3.0.

In this article, I focus on .NET Core 3.1 because this is the LTS release. The LTS means that this release of 3.1 has long time support compared to 3.0.

On the SDK,, I use .NET Core 3.1.100 SDK at the time of this writing.

To create a new F# project to have WinForms and WPF, I could just use console as template. Because currently no default template support for F# for WinForms and WPF.

We should use global.json to enforce the SDK to be 3.1.100, then create the project, like this:

dotnet new console -n FSWinfowsDesktop -lang F#

By default, console project of F# and C# always have base SDK in the <Project> header.
Now let's look at the content of FSWindowsDesktop:



We can leverage the "FrameworkReference" to have references to .NET Core SDK libraries in a separated contexts as previously described as metapackage before 3.0. This is an often overlooked features, although it is very useful!

The Framework reference in .NET Core 3.0/3.1 describes what libraries we want to use besides the base .NET Core libraries. Using VS 2019 16.3.x or later, the FrameworkReferences is shown nicely as "Frameworks":


The Microsoft.NETCore.App is always referenced by default, because this FrameworkReference is the base .NET Core library.

The available valid FrameworkReferences values are available under the Dotnet installation folder under "Shared" of "Program Files" for 64-bit, or "Program Files (x86)" for 32-bit.



Now let's add the Microsoft.WindowsDesktop.App as FrameworkReference, and also explicitly tell the project to use WinForms using <UseWindowsForms> set to true.

We now have this:


Then we can now leverage the references inside our F# code, using the open namespace and we are also guaranteed to have the namespaces available in the intellisense!


To use WPF, we can use <UseWPF> and set to true.
Also we can have both WPF and WinForms by having both <UseWindowsForms> and <UseWPF> both set to true, as long as we have FrameworkReference to Microsoft.WindowsDesktop.App.

To see this sample as a complete sample with the global.json file, I have made the source code available on my GitHub:
https://github.com/eriawan/netcore-fsharp-sample

And last but not least, Merry Christmas and have a happy F# Advent 2019!

😀

No comments:

Post a Comment