Run WPF in .NET Core on Nano Server in Docker

Every now and then you’re presented with a scenario that should in theory work as advertised, but you’re not convinced until you actually see it with your own eyes. I recently found myself in this situation when migrating some code from the full .NET Framework running on a traditional Windows Server cluster to .NET Core running as a microservice on Kubernetes.

You can download the code for this post from my hello-netcore-wpf-nano GitHub repo. The Dockerfile for the Windows Desktop Nano Server container image on Docker Hub can be found in my dotnet-runtime-windowsdesktop GitHub repo.

The code in question is non-visual but has a dependency on WPF (Windows Presentation Foundation), in particular the Geometry classes in the System.Windows.Media namespace. Because .NET Core version 3 and later includes WPF, and Nano Server (a compact version of the Windows operating system designed to run in containers) supports .NET Core (but not the full .NET Framework), it should be possible to run a non-visual WPF application on Nano Server in a Docker container — and to deploy it to Kubernetes using Amazon EKS, which has support for Windows containers.

.NET Core WPF Console App

The first step is to create a WPF console app using .NET Core, which is a rather unnatural act, since WPF is normally used to create a visual UI. You need to start out with a WPF .NET Core Library, using either Visual Studio or the .NET Core CLI.

dotnet new wpflib -n MyWpfConsoleApp

Running this command on Windows — don’t even think of doing it on a Mac — will produce a class library project based on the Windows Desktop SDK. To convert it to a .NET Core console app all you need to do is edit the csproj file and set the project output type to Exe.

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

Then add a Progam.cs file with a static Main method in which you use some Geometry classes.

using System;
using System.Windows.Media.Media3D;
namespace MyWpfConsoleApp
{
public static class Program
{
public static void Main()
{
var point = new Point3D(1, 2, 3);
Console.WriteLine($"Point X: {point.X}, Y: {point.Y}, Z: {point.Z}");
}
}
}

Executing dotnet run will produce the following output.

Point X: 1, Y: 2, Z: 3

Dockerized WPF Console App

To use Docker you must first install Docker Desktop for Windows.

To Dockerize your WPF console app, start by adding a Dockerfile to the project. If you are using Visual Studio, ordinarily you would right-click the project in the Solution Explorer and select Add Docker Support. But this won’t work with a WPF app.

A more feasible approach is to open the project folder in Visual Studio Code, where you have installed the Docker extension. Press Ctrl+P to open the command pallet, type Docker and select Add Docker files to Workspace.

Select .NET Core Console for Windows and you’ll get a Dockerfile that looks like this.

FROM mcr.microsoft.com/dotnet/core/runtime:3.1-nanoserver-1909 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-nanoserver-1909 AS build
WORKDIR /src
COPY ["MyWpfConsoleApp.csproj", "./"]
RUN dotnet restore "./MyWpfConsoleApp.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "MyWpfConsoleApp.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyWpfConsoleApp.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyWpfConsoleApp.dll"]

Build and run the container.

docker build -t wpf-console-app .
docker run --rm --name wpf-console-app wpf-console-app

At this point you’ll get an error complaining that the Microsoft.WindowsDesktop.App framework could not be found.

It was not possible to find any compatible framework version
The framework 'Microsoft.WindowsDesktop.App', version '3.1.0' was not found.
You can resolve the problem by installing the specified framework and/or SDK.

This is because the Windows Desktop framework is not installed on the Nano Server base image. For that you’ll need to create a custom base image that installs the Windows Desktop framework on Nano Server (or simply use my Windows Desktop Nano Server container image). Here is the WinDesktop.Dockerfile I created. It downloads and runs the installer for the Windows Desktop runtime, then copies the dotnet folder from the installer image to the Nano Server runtime image.

# escape=`
# Installer image
FROM mcr.microsoft.com/windows/servercore:1909 AS installer
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
# Retrieve .NET Core Runtime
# USER ContainerAdministrator
RUN $dotnet_version = '3.1.5'; `
Invoke-WebRequest -OutFile dotnet-installer.exe https://download.visualstudio.microsoft.com/download/pr/86835fe4-93b5-4f4e-a7ad-c0b0532e407b/f4f2b1239f1203a05b9952028d54fc13/windowsdesktop-runtime-3.1.5-win-x64.exe; `
$dotnet_sha512 = '5df17bd9fed94727ec5b151e1684bf9cdc6bfd3075f615ab546759ffca0679d23a35fcf7a8961ac014dd5a4ff0d22ef5f7434a072e23122d5c0415fcd4198831'; `
if ((Get-FileHash dotnet-installer.exe -Algorithm sha512).Hash -ne $dotnet_sha512) { `
Write-Host 'CHECKSUM VERIFICATION FAILED!'; `
exit 1; `
}; `
`
./dotnet-installer.exe /S
# Runtime image
FROM mcr.microsoft.com/windows/nanoserver:1909
ENV `
# Enable detection of running in a container
DOTNET_RUNNING_IN_CONTAINER=true
# In order to set system PATH, ContainerAdministrator must be used
USER ContainerAdministrator
RUN setx /M PATH "%PATH%;C:\Program Files\dotnet"
USER ContainerUser
COPY --from=installer ["/Program Files/dotnet", "/Program Files/dotnet"]

Note: If you create your own custom container image using this Dockerfile, you will need to push it to a container registry of your choice, such as Docker, Azure, Amazon or Google.

To get your local Dockerfile to work, replace the FROM statement at the top with one that specifies the custom Docker image you created (or you can reference my base image).

FROM tonysneed/dotnet-runtime-windowsdesktop:3.1-nanoserver-1909 AS base

After re-running the docker build and docker run commands shown above, you should see output from your WPF console app.

Congratulations. You now have a Nano Server container running a .NET Core app that references WPF.

Happy coding!

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged , , , . Bookmark the permalink.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.