BlazorOnWPF-870x220

There is a lot of promise in bringing Blazor goodness to WPF desktop apps. This would be a wonderful way to modernize WPF apps and share code better between web/desktop.

Windows Presentation Foundation (WPF) is a full-stack UI framework for Windows apps, initially released as a part of .NET Framework 3 back in 2006. Present-day WPF is an open-source C#/XAML technology stack with matured tooling and a rich ecosystem. For 15+ years, WPF has served as a cornerstone of Windows desktop development and provides a happy productive place for most .NET desktop app developers.

Yet, one cannot help but acknowledge the cool new thing for .NET developers—Blazor is eating the world. With Blazor, developers get to build modern web apps with C#/.NET, running on server or fully client-side through WebAssembly. But could Blazor have a play in the WPF technology stack toward building desktop apps? Could developers share code between web and desktop, and have a way to modernize WPF apps? While early days, the answer is yes—there are several promising ways to bring Blazor goodness into WPF apps. Let’s explore the possibilities.

WPF Is Fine

First, let’s address the obvious question that would be asked—is WPF ok? My opinion: It is more than fine actually. While there are other C#/XAML Windows desktop technology stacks, like UWP and WinUI, none has the experience and maturity that WPF can boast. WPF provides .NET developers a fantastic development experience with top-notch IDE support in Visual Studio, matured tooling and a rich community ecosystem.

As for runtimes, WPF apps will run happily on the full .NET framework for years to come. If your enterprise workflow includes a WPF solution that is doing the job fine without the need for major updates, you should be more than fine running WPF as is on .NET Framework.

If, however, your WPF app could benefit from newer Windows API usage, packaging .NET runtime with app or shiny UI components through XAML Islands, you have the option of incrementally modernizing your WPF app. With the help of tools like the .NET Upgrade Assistant, WPF apps can run on .NET Core 3.0, .NET Core 3.1 LTS, .NET 5 and even be future-facing with .NET 6. So, if need be, WPF can run on the very latest .NET stacks and power modern desktop solutions. One more time—WPF is fine.

WebView2 Embedded in WPF

Let’s face the reality though—web technologies are cool and .NET developers are understandably excited about the present/future of Blazor. Perhaps you have tinkered with Blazor or have team members working on a Blazor project? Could you bring in some web goodness into WPF? Let’s look at the lowest hanging fruit—we could shove web content inside a WPF app. This would be true not just for Blazor apps, but any web content, like SPA applications written in Angular/React/Vue/others.

The trick is the modern WebView component—aka WebView2 on Windows 10. WebView2 is driven by the same Chromium engine that powers Microsoft Edge browser—it can handle the best of the modern web. It is very easy to add a WebView2 component inside an existing WPF app and start injecting web content. Developers need to have WebView2 installed on local machine and bring in the Microsoft.Web.WebView2 NuGet package.

NuGet: WebViewOnWPF MainWindow.xaml, under Installed is Microsoft.Web.WebView2.

With the WebView2 component in place, developers can add the corresponding namespace in the MainWindow.xaml WPF startup page or whichever view they want to use WebView 2 in. Then, all that is left to do is embed the WebView2 component inside a container control and point the Source property to a hosted URI, like so:

<Window x:Class="WebViewOnWPF.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
    xmlns:local="clr-namespace:WebViewOnWPF"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

    <DockPanel DockPanel.Dock="Top">
    <wv2:WebView2 Name="webView"
              Source="https://telerik.com"/>
    </DockPanel>
</Window>

Fire up the app and voila—web content inside a WPF app!

MainWindow shows Progress Telerik page.

Injecting web content inside a WPF app does not need to be a one-way street—the host app and the web content can interact. The WPF host can listen in on lifecycle events raised by WebView2 and inject JavaScript code into WebView2 controls at runtime. One can also enable communications between host and web content through the exchange of event-based messages, thus providing richness of experiences. Injecting web content through WebView2 hosting is perhaps the easiest way to bring in Blazor content into WPF apps—while there are limitations, it clearly works.

WPF with Blazor Hybrid

Late last year saw the advent of something with a ton of potential—Blazor Mobile Bindings. While experimental, the goal was to use Razor syntax and the Blazor component model toward building native/hybrid mobile and desktop apps. Developers got to render native platform UI through Xamarin.Forms with Blazor bindings or render straight-up Blazor web components inside a modern WebView—aka WebView2 on Windows or WKWebView on MacOS.

With the BlazorHybrid project template, developers get a single solution for an app that runs everywhere, with the Windows app being a WPF app. The little magic happens through WPF Renderers for Xamarin.Forms, which know how to render native WPF UI.

using Microsoft.MobileBlazorBindings.WebView.Windows;
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.WPF;

namespace BlazorChat.Windows
{
    public class MainWindow : FormsApplicationPage
    {
        [STAThread]
        public static void Main()
        {
        var app = new System.Windows.Application();
        app.Run(new MainWindow());
        }

        public MainWindow()
        {
        Forms.Init();
        BlazorHybridWindows.Init();
        LoadApplication(new App());
        }
    }
}

Beyond the app being bootstrapped, Razor syntax can be used to render native UI for WPF, and Blazor content is very welcome being injected inside a WebView component, like so:

@inject CounterState CounterState

<ContentView>
<StackLayout>

    <StackLayout Margin="new Thickness(20)" Orientation="StackOrientation.Horizontal">
        <Label Text="@($"Hello, World! {CounterState.CurrentCount}")" FontSize="40" HorizontalOptions="LayoutOptions.StartAndExpand" />
        <Button Text="Increment" OnClick="@CounterState.IncrementCount" VerticalOptions="LayoutOptions.Center" Padding="10" />
    </StackLayout>

    <BlazorWebView VerticalOptions="LayoutOptions.FillAndExpand">
        <BlazorChat.WebUI.App />
    </BlazorWebView>

</StackLayout>
</ContentView>

@code 
{
    protected override void OnInitialized()
    {
    CounterState.StateChanged += StateHasChanged;
    }

    public void Dispose()
    {
    CounterState.StateChanged -= StateHasChanged;
    }
}

The end result is a WPF app with native and hybrid web UI mixed in, if desired. This is a native WPF app running on Windows and can do all the native API things that WPF apps usually enjoy.

My Application shows a Hello, World! 0, with an increment button. Below that the screen is divided. A small purplish menu on the left is called My Hybrid App, with Home, Counter (which we're on), Fetch data, and Chatroom. The Counter page shows Current count: 0, with a Click me button. A short bar runs across the top of the Counter section and has a link to About on the right side.

It is worth mentioning though that the future of bringing Blazor to WPF through Xamarin.Forms Renderers may be questionable. The evolution of Xamarin.Forms is .NET MAUI running on .NET 6, slated for November 2021 release. .NET MAUI promises a stable cross-platform story to reach iOS, Android, Windows and MacOS—from a single codebase.

The chosen technology stack for .NET MAUI to reach Windows desktop is not WPF though—it is WinUI 3. This is very understandable, given WinUI 3 is the most modern fluent-inspired end-to-end UI stack for Windows apps. The trick to bringing Blazor in to Windows desktop is the same though—stick in a BlazorWebView in the visual tree that renders the best WebView component on each platform.

A WinUI Desktop window shows a page with a horizontal bar labeling the page  MauiBlazorApp, and a link to About on the right. Below the bar there is a purplish menu on the left with Home (which we're on), Counter and Fetch data. The page to the right shows Hello, world! Welcome to your new app. Then a gray box with How is Blazor working for you? Please take our brief survey (linked) and tell us what you think.

BlazorWebView in WPF

Don’t have the appetite to rewrite an app from scratch with .NET MAUI or migrate over to WinUI 3? Not to worry—your existing WPF app has a long shelf life and allows you to bring in Blazor goodness anyway. All you have to do is run your WPF app on .NET 6—the .NET Upgrade Assistant is ready to help.

Once on .NET 6, your WPF apps will earn a magical new control—surprise, it’s the BlazorWebView. The new BlazorWebView for WPF works exactly the same way as it does for .NET MAUI apps—it provides an easy way to embed Blazor content inside Windows desktop apps.

Blazor functionality can actually be built right inside your WPF app project—that’s Razor pages with C#/HTML, CSS and, if need be, some JavaScript, all enabling a desktop WPF solution. This is an interesting way to potentially modernize your WPF apps—keep the .NET investments and bring in Blazor as desired. Let’s see how this works. To prove the point, we start with a vanilla WPF app running on .NET Framework, like so:

Screen for Configure your new project, WPF App (.NET Framework) with tags C#, XAML, Windows, Desktop. Project name, Location, Solution name, Framework fields are all filled.

Next up, we get the .NET Upgrade Assistant to work—install the CLI tool, navigate in Terminal to project directory and fire away:

upgrade-assistant upgrade .\BlazorWebViewOnWPF.csproj

The near-magical upgrade tool tells you all the steps that are need to migrate the runtime framework and then actually does the work. The .NET Upgrade Assistant can be used to have the WPF app migrate from running on .NET Framework all the way to running on .NET 5. Once done, you can open the .csproj file to see the changes.

The last step is to force it to run on .NET 6 Preview, granted you have the runtime installed—we can simply change the TargetFramework. We also need to update the project SDK to Microsoft.NET.Sdk.Razor and bring in a NuGet package dependency of Microsoft.AspNetCore.Components.WebView.Wpf. Here’s how the updated project file should look:

A look at some code on BlazorWebViewOnWPF.csproj

Next up, we can start adding web-thingies to our WPF project. A wwwroot folder can hold our HTML views and any needed CSS styles, like so:

Solution Explorer shows Solution 'BlazorWebViewOnWPF' project. We're looking at the menu with BlazorWebViewOnWPF expanded to show dependencies, properties, wwwroot, etc.

Here is the barebones index.html file:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>Blazor app</title>
    <base href="/" />
    <link href="BlazorWebViewOnWPF.styles.css" rel="stylesheet" />
    <link href="app.css" rel="stylesheet" />
</head>

<body>
    <div id="app"></div>
    <script src="_framework/blazor.webview.js"></script>
</body>
</html>

Next up, no Blazor functionality is complete without the classic Counter.razor component - this could live under the root folder:

@using Microsoft.AspNetCore.Components.Web
<h1>Counter</h1>

<p>The current count is: @currentCount</p>
<button @onclick="IncrementCount">Count</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

The code-behind file needs to simply initialize the Counter—this likey helps WPF find the component:

using System;

namespace BlazorWebViewOnWPF
{
    public partial class Counter { }
}

Next, we turn to the classic MainWindow.xaml file to define the XAML visual tree of our first (and in this case, only) view. We add a namespace to bring in the WebView and add the BlazorWebView control to a container layout parent, like a Grid:

<Window x:Class="BlazorWebViewOnWPF.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
    xmlns:local="clr-namespace:BlazorWebViewOnWPF"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

<Grid>
    <blazor:BlazorWebView HostPage="wwwroot/index.html" Services="{StaticResource services}">
        <blazor:BlazorWebView.RootComponents>
            <blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Counter}" />
        </blazor:BlazorWebView.RootComponents>
    </blazor:BlazorWebView>
</Grid>
</Window>

In MainWindow.xaml.cs, the service provider needs to be added as a static resource so Blazor components can be bootstrapped:

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace BlazorWebViewOnWPF
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddBlazorWebView();
            Resources.Add("services", serviceCollection.BuildServiceProvider());

            InitializeComponent();
        }
    }
}

That’s it. All said and done, we fire up the app. And tada—Blazor component injected inside WPF app. And all of the Blazor functionality is built within the same project and styled as a desktop app.

BlazorWebViewApp Main Window shows Counter, The current count is: 0, with a Count button.

Can I Have Some UI?

Now, let’s address the elephant in the room, albeit partially. It is great that Blazor is now very welcome on desktop, in particular WPF apps. Developers can mix/match C#/XAML code with Blazor web code and modernize desktop apps. But what about custom Blazor components—can they come over? Developers should not reinvent the wheel, and Telerik UI for Blazor really helps with polished, performant and complex Blazor UI components—done right out of the box.

So, would Telerik UI for Blazor components work inside a WPF app? Early days. All we can say is things look very promising. Stay tuned y’all.

Conclusion

A lot more could be said. Developers can get into heated conversations. We could talk about pros/cons of each approach. We could dissect the Why and the How.

But here are the cold hard facts: WPF is here to stay and Blazor is cool. And they can both coexist happily. That should be all we need to get excited about the future.


SamBasu
About the Author

Sam Basu

Sam Basu is a technologist, author, speaker, Microsoft MVP, gadget-lover and Progress Developer Advocate for Telerik products. With a long developer background, he now spends much of his time advocating modern web/mobile/cloud development platforms on Microsoft/Telerik technology stacks. His spare times call for travel, fast cars, cricket and culinary adventures with the family. You can find him on the internet.

Related Posts

Comments

Comments are disabled in preview mode.