🙌

.NET7でBlazorWebViewとWPFによるハイブリッドアプリのサンプル

2023/07/21に公開

Blazorの登場から随分時間が経過してしまいました。
C#でWebアプリケーションの実装ができるこの技術、とてもおもしろいですね。

本記事では、BlazorWebViewを使用しBlazorのコードを動作させつつ、WPFアプリケーションの中に埋め込むサンプルを紹介します。
Visual Studio 2022と.NET7を使い、WPFの都合でWindowsのみを対象しています。

BlazorWebView

BlazorWebViewは、.NETのBlazor Web UIフレームワークをデスクトップアプリケーションで使用できるようにするコンポーネントです。
これは、Blazorのコンポーネントをネイティブデスクトップアプリケーションでレンダリングし、同じコードベースでWebとデスクトップの両方を対象とすることを可能にします。

Blazor自体は、下位層にWebAssemblyやJavaScriptを用いて実行するWeb UIフレームワークで、C#とHTML、CSSを使用してWeb UIを構築できます。
しかし、BlazorWebViewを使用することで、BlazorのコンポーネントをローカルのWebViewコントロールに表示できます。
これにより、デスクトップアプリケーションがBlazorのフル機能を利用でき、コンポーネントの更新やUIの変更を簡単に行うことができます。

プロジェクトの作成

WPF+BlazorWebViewという組み合わせでのテンプレートは用意されていないため、手作業で整える必要があります。GitHubを検索してみると、初期テンプレートを用意してくれるものがいくつか見つかるので、そちらをインストールして使うのも1つの手です。

ここではWPFアプリケーションのテンプレートをもとにはじめます。

作成された各種コードや設定ファイルなどを変更していきます。

NuGet でパッケージの追加

BlazorWebViewのために、NuGetで「Microsoft.AspNetCore.Components.WebView.Wpf」のパッケージをインストールします。

csprojの設定変更

csprojファイルを開いて、冒頭にあるSDK属性を以下のように変更します。
Visual Studioのソリューションエクスプローラーからファイルを選んで、右クリック「プロジェクトファイルの編集」を使うと便利です。

旧: <Project Sdk="Microsoft.NET.Sdk">
↓
新: <Project Sdk="Microsoft.NET.Sdk.Razor">

またこの後に配置するwwwrootフォルダのコピー設定も追記しておきます。

<ItemGroup>
  <Content Update="wwwroot\**">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Blazorのコードを追加する

App クラスの編集

App.xaml.csファイルを開き、以下のコードを追加します。

private void Application_Startup(object sender, StartupEventArgs e)
{
  var serviceCollection = new ServiceCollection();
  serviceCollection.AddWpfBlazorWebView();
  Resources.Add("services", serviceCollection.BuildServiceProvider());
}

App.xamlファイルを開き、追加した関数をStartupに設定します。

<Application x:Class="WpfAppHybrid.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfAppHybrid"
             Startup="Application_Startup"
             StartupUri="MainWindow.xaml">

WebViewを配置する

MainWindow.xamlを開いて、BlazorWebViewを追加します。
ここでは下記のように編集しました。

<Window x:Class="WpfAppHybrid.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:WpfAppHybrid"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <blazor:BlazorWebView x:Name="blazorWebView" HostPage="wwwroot\index.html" Services="{DynamicResource services}">
            <blazor:BlazorWebView.RootComponents>
                <blazor:RootComponent Selector="#app" ComponentType="{x:Type local:MyPage}"/>
            </blazor:BlazorWebView.RootComponents>
        </blazor:BlazorWebView>
    </Grid>
</Window>

wwwrootフォルダ以下の準備

csprojと同じ場所にwwwrootフォルダを作成して、中にindex.htmlファイルを配置します。

<!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, viewport-fit=cover" />
    <base href="/" />
    <link href="app.css" rel="stylesheet" />
    <link href="WpfAppHybrid.styles.css" rel="stylesheet" />

    <title>WpfAppHybrid1</title>
</head>
<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

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

続いてapp.cssファイルを同じように作成し、内容を以下の通りとします。

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

Razorファイルを配置する

BlazorではRazorテンプレートを用いてページを作ります。ここではMyPage.razorとしてファイルを作成しました。

@using Microsoft.AspNetCore.Components.Web

<h3>Hello,WPFApplication</h3>

<p>@Message</p>
<button @onclick="OnClicked">Click button</button>

@code {
	private string Message = "Hello,hybrid world";

	private void OnClicked()
	{
		Message = "ボタンが押されました。";
	}
}

実行結果

実行すると、以下のような画面が表示されます。ウィンドウ内に表示されているのはBlazorWebViewで、Razorテンプレートで書かれたページ内容です。
ボタンをクリックすると、C#で記述したコードが実行されて表示が変化します。

まとめ

うまく作業が進めばなんの問題もないWPF+BlazorWebViewによるハイブリッドアプリですが、実際には割と手間取っていました。
WPFの場合は、 AddWpfBlazorWebView で追加することや @using Microsoft.AspNetCore.Components.Web の漏れだったりでしょうか。今回の手順でうまくいくとは思いますので、本記事が同じようにはまった人の助けになれば幸いです。

次回は、Razor内に閉じないC#のデータのやり取りについて紹介できればと考えています。

参考

https://zenn.dev/microsoft/articles/dotnet6-pre3-blazorwebview

https://github.com/dotnet/maui/issues/7589

Discussion