.NET 6 Preview 3 の Windows アプリに Blazor を埋め込む BlazorWebView を試してみた
こちらの記事の内容を動画にしました。
本文
🎉.NET 6 Preview 3 が出ましたね!🎉
ASP.NET Core updates の方のブログ記事の中に以下の一文を見つけました。
BlazorWebView controls for WPF & Windows Forms
ということで試してみました。
元々公開しようとは思ってなかったのでプロジェクト名が WpfApp1 ですが、実際に動くようになったコードは以下に公開しています。
実際に動かすと以下のようになります。上部分が WPF アプリで下部分が Blazor で作った画面になっています。カウンターの値が WPF と Blazor の画面間で共有できていることがわかると思います。
WPF アプリの場合はプロジェクト ファイルの Sdk 属性を Microsoft.NET.Sdk.Razor
に変更して Microsoft.AspNetCore.Components.WebView.Wpf
パッケージを追加すれば使えるようになります。後は index.html や razor ファイルを追加して開発が出来ます。まだ razor ファイルのインテリセンスが凄く弱かったりしますが、ここらへんは追々改善されていくと思います。
プロジェクト構造的には以下のような雰囲気になります。xaml と razor ファイルが同居してるのが新鮮ですね。
ポイントとしては ServiceCollection
をアプリの何処かで初期化して BlazorWebView
に設定することで、ServiceCollection
に設定したものを Blazor 側で使うことが出来ます。(もちろん、WPF と Blazor のアプリは同じプロセスで動いているので static 変数や他の方法でインスタンスの共有は出来ますが行儀はよくないかも)
WPF でも ServiceCollection
に登録したものを使うことで Blazor 側と同じインスタンスを取得してステートなどの共有も出来ます。
作成したサンプルでは、CounterState クラスを razor ファイル内で使用しています。
@implements IDisposable
@inject WpfApp1.CounterState CounterState
<h1>ここは Web !!</h1>
<p>The current count is: @CounterState.Value</p>
<button @onclick="IncrementCount">Count</button>
@code {
protected override void OnInitialized()
{
this.CounterState.PropertyChanged += CounterStateChanged;
}
public void Dispose()
{
this.CounterState.PropertyChanged -= CounterStateChanged;
}
private void CounterStateChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) =>
this.StateHasChanged();
void IncrementCount()
{
CounterState.Increment();
}
}
WPF 側でも ServiceCollection
に登録してある CounterState
を DataContext
に設定して利用しています。
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var provider = App.Current.Resources["services"] as IServiceProvider;
this.DataContext = provider.GetRequiredService<CounterState>();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((CounterState)this.DataContext).Increment();
}
}
}
ServiceCollection
の初期化は App
クラスの Startup
イベントで行って Resources
に登録することでアプリ全体で共有するようにしています。
private void Application_Startup(object sender, StartupEventArgs e)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddBlazorWebView();
serviceCollection.AddSingleton<CounterState>(); // このインスタンスを Blazor と WPF で共有している
Resources.Add("services", serviceCollection.BuildServiceProvider());
}
Blazor の世界に渡すには BlazorWebView
の Services
プロパティに設定して渡しています。
<blazor:BlazorWebView
Grid.Row="1"
HostPage="wwwroot/index.html"
Services="{StaticResource services}">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent ComponentType="{x:Type local:Counter}" Selector="#app" />
</blazor:BlazorWebView.RootComponents>
</blazor:BlazorWebView>
まとめ
とりあえず試してみた感じですが、結構気軽に Blazor を使って画面を作って WPF (と、今回は試していませんが WinForms) に埋め込むことが出来るようになっています。
Web 系の技術だと簡単に出来るのに…という表現については、これを使うとうまくハマると簡単に実現できるケースもあるかなと思いました。
あとは「XAML ??なるほど、わからん。」という感じの Web 系よりの人にとっては、もう全面 BlazorWebView
にして開発してしまうのもいいと思います。そこら辺の匙加減は、自由に出来るのが素晴らしいですね。
.NET 6 は結構盛沢山で見ていて楽しいですね!
Discussion