Meet WebWindow, a cross-platform webview library for .NET Core
It's like Electron, but without bundling Node.js or Chromium, and without most of the the APIs.
My last post investigated ways to build a .NET Core desktop/console app with a web-rendered UI without bringing in the full weight of Electron. This seems to have interested a lot of people, so I decided to upgrade it to newer technologies and add cross-platform support.
The result is a little NuGet package called WebWindow that you can add to any .NET Core console app. It can open a native OS window (Windows/Mac/Linux) containing web-based UI, without your app having to bundle either Node or Chromium.
I’ve also decoupled it from Blazor. You can now host any kind of web UI inside the window. The repo contains a sample that uses Vue.js, and another that uses Blazor.
Caution: This library is super-pre-alpha quality. If you’re thinking of building something real with this, see the notes at the end of this post. So far, this is just another prototype.
“Hello World” example
Create a new .NET Core 3 C# console application, and then add a reference to the WebWindow
NuGet package:
<ItemGroup>
<PackageReference Include="WebWindow" Version="0.1.0-20191120.3" />
</ItemGroup>
Next, add code to the Main
method in your Program
class.
static void Main(string[] args)
{
var window = new WebWindow("My super app");
window.NavigateToString("<h1>Hello, world!</h1> This window is from a .NET Core app.");
window.WaitForExit();
}
That’s it! Now depending on which OS you’re running, your app will display a window like one of the following:
The example here uses NavigateToString(html)
to render some HTML from a hardcoded .NET string
. You can also use:
NavigateToUrl(url)
to display content from an HTTP server (local or remote)NavigateToLocalFile(path)
to display an HTML file from the local disk, wherepath
is absolute or relative to the current working directory. The HTML file can reference other resources such as images, JS, CSS, etc., relative to its own location on disk. Example here.
As a slightly more advanced option, you can configure the WebWindow
to handle a custom scheme such as myapp://
and specify a delegate (callback) that returns arbitrary content for each URL within that scheme. Example here and here.
Once your web content is running, the low-level way to communicate between JavaScript and .NET is using the APIs window.external.sendMessage
/receiveMessage
in JS (example) and webWindowInstance.SendMessage
and webWindowInstance.OnWebMessageReceived
in .NET - (example). However if you’re building a Blazor app, you don’t need to use these low-level APIs and can use Blazor’s regular JS interop feature instead.
Hosting a Blazor app
WebWindow isn’t coupled to Blazor. Here’s an example of using Vue.js to render a simple directory-explorer app inside a WebWindow.
But if you do want to use Blazor, that’s extremely clean and easy. I’ve also made a small add-on package, WebWindow.Blazor, that lets you host a Blazor app with one line in your Program.Main
:
static void Main(string[] args)
{
ComponentsDesktop.Run<Startup>("My Blazor App", "wwwroot/index.html");
}
To recap, this does not involve WebAssembly, Node.js, or a privately-bundled copy of Chromium. It’s just .NET Core running natively, communicating directly with the OS’s own web rendering technology. The result, this time in macOS:
The complete WebWindow+Blazor sample is here.
How it works
- On Windows, WebWindow uses the new Chromium-based Edge via webview2, assuming you have that browser installed (it could fall back on older Edge if you don’t, but I haven’t implemented that)
- On Mac, it uses the OS’s built-in WKWebView, which is the same technology behind Safari
- On Linux, it uses WebKitGTK+2, which is yet again a WebKit-based technology
The whole point of this, compared with using Electron, is to produce apps that are smaller to download and use less memory. But does it actually? Here are the stats for download size:
As you can see, whether you choose “standalone” (bundles a copy of the .NET Core runtime) or “framework-dependent” (rely on .NET Core being installed in the target OS) makes a vast difference to the resulting app size. Framework-dependent WebWindow apps can be truly tiny, since they only contain your own app’s binaries and aren’t bundling either a runtime or a browser.
And now, stats for memory use:
On Windows, WebWindow and Electron are using the same browser technology (Chromium), which eats up most of the memory. That explains why the difference between them isn’t huge. On Linux and Mac, the difference between using a self-bundled browser and the OS’s built in technology is more substantial.
Will this be supported and maintained?
Currently I’m not making any promises! It’s best to think of it as yet another experiment for now. It’s possible that if enough other people want to get involved, it would be possible to create a proper open-source community project.
What’s most urgently needed is someone with C++ experience to come and rewrite my prototype-quality C++ and Objective-C code the way it actually should be done. The chance that I’ve got all the memory management right here is close to zero. Maybe it should use CMake or another sane build config system too. (Note: it does have a cross-platform CI build on Azure DevOps though.)
There’s also a large number of features you’d really want to add if you intended to use this in production. For example, the ability to set an app icon, to add a native menu bar, and so on. If you’re interested in contributing such functionality and will make it work cross-platform, please head over to the repo!