Bringing your Blazor apps to the Desktop with ElectronNET.Blazor
Harness the power of ASP.NET MVC to build Desktop apps that run on any OS.
One of the things that excites me most about the possibilities of Blazor is the potential for building apps with the speed and power of .NET and the ease of HTML, but running on any OS. Electron.NET has the potential to take that one step further by making your Blazor apps deployable as Desktop apps. Changes to Blazor Preview 6 have broken Electron.NET’s Blazor support for most apps. So I wrote a simple NuGet package called ElectronNET.Blazor that brings together everything you need to make extending your Blazor apps smooth and painless.
I should start off by saying that I didn’t do the hard work on this. Maher Jendoubi has an amazing post on how to get Electron.NET added to the BlazingPizza app. When those instructions didn’t work for my app, I suspected it had something to do with the routing not lining up with what Electron expected.
Troubleshooting the White Screen of Death
I knew very little about how Electron, Electron.NET, or the new Endpoint Routing worked. Fortunately for me .NET Core is awesome, everything involved is open source, and Electron windows let you open the Chromium Dev Tools, so I had everything I need to dig in and figure it out.
Debugging the “Electron.NET App” profile brings up a Visual Studio console window, which helpfully displays the content root path as ASP.NET Core’s web server starts up. Digging through that folder, it was clear that the files were getting copied over, but the folder that was being set as the “WebRoot” was too shallow to get to the files. I was able to confirm this by adding app.UseDirectoryBrowser()
to Startup.Configure.
The directory just below “_content” is “wwwroot”, which is the default folder for .NET Core content. The new call to endpoints.MapFallback ToClientSideBlazor<Client.Startup>("index.html")
in Startup.Configure handles that for you. Great if you’re serving up from an Azure website, not so great if you’re in an Electron-hosting Chromium shell.
Solving the Problem
So there are really two issues going on here:
- The first issue is that based on where Electron.NET packs things, the web server has no idea where the assets it needs are.
- The second issue is that Blazor isn’t pointing to the right Fallback route relative to the WebRoot.
Fortunately, we can use the design of the new startup extensions to our advantage. But in order to do that, we have to differentiate between when the app is running in Electron, and when it isn’t. The easiest way (for now) to do that is to add an Environment Variable to the “Electron.NET App” profile created when you initialize Electron.NET.
Now that we can tell the difference, when we’re starting up as Electron, we need to first tell .NET Core to serve up the files in the right root:
var assemblyName = typeof(TClientApp).Assembly.GetName()
.Name.ToLower().Replace(“.”, “”);builder.UseStaticFiles(
new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(
Directory.GetCurrentDirectory(),
$”wwwroot\\_content\\{assemblyName}”
)
),
RequestPath = “”
}
);
Note that when I turned on Directory Browsing, the Client app assembly name (sans periods) was in the path. So we first have to calculate that. Then, we have to set the StaticFile provider to the currently executing assembly’s path plus the path to the content folder.
It’s important to note that this code doesn’t replace the call to app.UseClientSideBlazorFiles<Client.Startup>();
. That’s because we still need to map OTHER files that are part of the Client app, like the Mono runtime and your assemblies. We just make our new calls first, so they are available to the runtime before the other code executes.
Then, we need to tell .NET Core the right fallback path to use.
var assemblyName = typeof(TClientApp).Assembly.GetName()
.Name.ToLower().Replace(“.”, “”);builder.MapFallbackToClientSideBlazor<TClientApp>($"_content/{assemblyName}/{path}");
This time, we’re going from the route mapped internally (again, from the “wwwroot” folder) to the actual file location.
Notice that we didn’t actually have to write a whole lot of new code, we just had to map the right paths into the existing Blazor registration and mapping functions. It’s a testament to the solid design of .NET Core 3.0.
The end result is, to Electron-ify any Blazor app, you basically have to tack on 3 lines of code (albeit, added in 3 different places):
webBuilder.UseElectron(args);app.UseElectronNETStaticFiles<Client.Startup>();endpoints.MapFallbackToClientSideElectronNET<Client.Startup>("index.html");
Those last two methods automatically detect if the app isn’t running Electron, and registers everything normally, keeping your code fresh and clean. Pretty slick, right?
Wrapping It All Up
So, if you’re building a Blazor app, you should DEFINITELY be thinking about the ASP.NET Core Hosted pattern, so you can turn it into a desktop app. That template also helps mitigate any issues with code that won’t run inside WASM… but that’s another blog post for another time.
In the meantime, give ElectronNET.Blazor a shot in your next Blazor app, and say hello to lightning fast, observable, no JavaScript apps on any desktop! And don’t forget to check out BlazorEssentials, an MVVM toolkit for Blazor apps that makes building highly-testable apps a breeze.