Introduction

Blazor is becoming little by little the defacto standard for developing new and modern SPA applications inside the .NET ecosystem which becomes cross-platform. It empowers the creativity of developers to achieve more by targeting multiple platforms and devices using the same code base. That ambition leads to exploring the existing programming models not only for Web or Cloud development but also for building desktop apps.

Mr. Steve Sanderson wrote recently a detailed blog post about a proof-of-concept on using a webview to host a Blazor desktop app on Windows. In this blog post, I will try to dissect his POC and share my personal opinion about it. In addition, I will attempt to answer his important couple of questions.

Prerequisites

The original experiment was written using an older version of Blazor which I updated using the latest cutting edge versions of both framework and tooling. The template used for the starting project MyDesktopApp is a Blazor client-side template. This template is referencing 2 .NET standard libraries.

  • Visual Studio 2019 version 16.4 Preview 2.0
  • .NET Core SDK 3.1.100-preview1-014459
  • Windows 10 version 1903
  • Blazor 3.1.0-preview1.19508.20:
<?xml version="1.0" encoding="utf-8"?>
<Project>
  <PropertyGroup>
    <BlazorPackageVersion>3.1.0-preview1.19508.20</BlazorPackageVersion>
  </PropertyGroup>
</Project>
  • "@dotnet/jsinterop": "3.1.0-preview1.19506.1"

It could be a nice pull request!

POC's dissection

Blazor desktop apps development could be possible using Electron by combining the powers of Nodejs and Chromium. I wrote a previous blog post about this subject and I discovered through the feedback of the community the heavy footprint of such desktop applications due to Nodejs, Chromium and .NET Core packages increasing the needed disk size. Therefore, the download size and the memory consumption of the desktop app will be increased.

The brilliant idea of Mr. Sanderson is to experiment with a lighter option than Electron for hosting Blazor desktop apps that relies on .NET Core. Concretely, this hosting alternative is achieved thanks to a native Windows webview. This control uses the Microsoft Edge rendering engine (EdgeHTML) or the System.Windows.Controls.WebBrowser, for devices on older versions (WebViewCompatible), to embed a view that renders richly formatted HTML5 content from a remote web server, dynamically generated code, or content files.

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Blazor" Version="$(BlazorPackageVersion)" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.0-preview1.19506.1" />
    <PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="6.0.0-rc1" />
    <EmbeddedResource Include="../Microsoft.AspNetCore.Components.Desktop.JS/dist/blazor.desktop.js" />
  </ItemGroup>

</Project>

From the above csproj file, we can see clearly the application hierarchy which is made of a Windows Forms desktop application that is running a webview that is web rendering a Blazor application. The integration of Blazor and the webview is done through an important file of more than 2500 lines of javascript code: blazor.desktop.js which leverages the Web.JS library.

<Project Sdk="Microsoft.NET.Sdk">
  ...
  <Target Name="RunWebpack" AfterTargets="ResolveReferences" Inputs="@(WebpackInputs)" 
          Outputs="dist\blazor.desktop.js" DependsOnTargets="EnsureNpmRestored">
    <Exec Command="npm run build:debug" Condition="'$(Configuration)' == 'Debug'" />
    <Exec Command="npm run build:production" Condition="'$(Configuration)' != 'Debug'" />
  </Target>
</Project>

The entry point of the application is the Main method which is decorated with [STAThread] annotation and contains a Run method to bootstrap the Blazor desktop app.

using Microsoft.AspNetCore.Components.Desktop;
using System;

namespace MyDesktopApp
{
    public class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            ComponentsDesktop.Run<Startup>("index.html", form =>
            {
                form.Text = "MyDesktopApp";
            });
        }
    }
}

The Run method of the static class ComponentsDesktop contains 2 parameters. The first is the index.html string. The second parameter is a lambda expression to name the application. After running the app, we get the following GUI :

The app looks native, incredibly fast running all the current Blazor features : data binding, routing, responsiveness/hamburger menu, js interop, etc.

My personal opinion

After dissecting the Blazor desktop app's POC and reading several times the corresponding blog post including the comments, the tweets, and each commit on GitHub, I would like to share my own personal viewpoint.

Electron has more than of 5 years of existence and it has tons of features out of the box: notifications, recent documents, application progress, custom dock menu, custom Windows taskbar, custom Linux desktop actions, keyboard shortcuts, online/offline detection, represented file macOS BrowserWindow, native file drag & drop, offscreen rendering, supporting macOS dark mode, etc. Thus, it is unfair to compare it to an experiment.

But the experiment is based on WebAssembly technology which is a game-changer making Blazor libraries very compact. It is leveraging the .NET Core performance.

"Do you have scenarios for building hybrid desktop apps with .NET+HTML+CSS?"

Absolutely, I could share the scenario of building a medical imaging viewer leveraging the capabilities of both APIs: Web and desktop. Maybe I could elaborate more on it in future blog posts. But, to give you a glimpse, I urge you to think a moment about the power of the responsiveness feature thanks to HTML and CSS when having different and multiple screen resolutions for clinicians and radiologists. Those types of users are strongly claiming fast tools to treat more patients and especially save urgent cases.

"Would you be happy to use Blazor with Electron, or do you feel it’s necessary to have something more bare-metal?"

As .NET is getting unified, C# language will become more and more powerful to target multiple platforms and to cover further customer needs. Furthermore, the design of new apps is becoming microservice-oriented. From now on, there are no more new monolithic apps unless we have a legitimate reason and this is true even for desktop apps. Let's go back to my medical imaging viewer scenario. I apologize in advance if this is a too specific scenario related to the healthcare field but trust me it is very realistic and concrete. I could design a microservice that is computing a volume rendering (VR). That VR microservice is hosted somewhere in the Cloud but my hybrid desktop app could stream the result and render it on the webview in realtime. If I have an increased number of users from year to year, you could imagine how architecture could be scalable. This could be possible to the dotnet CLI tooling, the .NET Core framework, the emergence of containers and cluster orchestrators.

There is another important aspect which is security. Using a reduced attack surface by decreasing the number of dlls will maximize the security of your app and your systems.

For these reasons, personally, I feel it's necessary to have something more bare-metal and I will be happy if such a project will be backed by both Microsoft and the community.

Conclusion

The design of a new bare-metal tool considering Blazor capabilities and microservice-oriented architectures is much better than adapting/tweaking an existing one to adapt it. Combining performance, productivity and security will lead inevitably to a successful tool. This is my personal viewpoint and I hope to know more about yours, dear readers.