WebAssembry上で動くSilverlight、OpenSilverで何が出来るのか

# はじめに

11/12 に Silverlight を WebAssembly で再実装した [OpenSilver](https://www.opensilver.net/) が[リリース](https://opensilver.net/announcements/opensilver-release.aspx)されました。この記事ではプロジェクトテンプレートのインストールと簡単なアプリケーションの作成を通じて OpenSilver がどのようなものなのかを確認していきます。

# OpenSilver と CSHTML5

GitHub の [OpenSilver のページ](https://github.com/OpenSilver/OpenSilver)を確認すると、このプロジェクトには OpenSilver とは別に CSHTML5 というプロジェクトも含まれていることが確認できます。

どちらも .NET 5 以降の C# や XAML を現在のモダンな Web ブラウザーでプラグインなしに動作させる物であることは変わりませんが、実際のアプローチにはかなり違いがあります。[OpenSilver](https://www.opensilver.net/) が.NET のアセンブリを Blazor WebAssembly の技術基盤の上で WebAssembly のまま動かすプロジェクトであるのに対し、[CSHTML5](http://www.cshtml5.com/) は.NET のアセンブリを一度 JavaScript に変換して動作させるプロジェクトになります。

# プロジェクトの位置づけ

WebAssembly 上で動作するアプリケーションの形態であれば、[Blazor WebAssembly](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?view=aspnetcore-5.0) や [UnoPlatform](https://platform.uno/) がすでにあります。これらに対し OpenSilver の位置づけはどのような物になるでしょうか？特に UnoPlatform であれば UWP ベースではありますが XAML/C# のアプリを動かすこともできます。

OpenSilver はあくまで既存の Silverlight アプリケーションの移行先として提供されているモノのようです。このため作成されるアプリの見た目も HTML5 ベースのブラウザー標準のものでは無く、これまでの Silverlight で作成したアプリと同じような見た目になります（もちろん XAML のスタイルに依存するわけですが）。また、開発技術的にも XAML と C# といったこれまでの技術要素がそのまま使えるため、既存チームの習得コストがあまり掛からないというメリットもあるでしょう。

ただし、Silverlight の移行先と言っても WebAssembry 上での制約で動作しなかったり、.NET Framework から .NET に変更になって動かなくなった機能もあるでしょう。また、まだ OpenSilver で実装されていない機能もあるようです。OpenSilver の[マイグレーションに関するドキュメント](https://doc.opensilver.net/documentation/migrate-from-silverlight/overview.html)が用意されているので一度確認してみてください。

# プロジェクトテンプレートのインストール

OpenSilver にはプロジェクトテンプレートが用意されているので、Visual Studio の`拡張機能` -> `拡張機能の管理`から OpenSilver を検索してインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/1f7516a8-0392-bc4b-8a52-149593855067.png)
[OpenSilver のダウンロードページ](https://www.opensilver.net/download.aspx)からインストーラーをダウンロードしてインストールすることもできます。

https://www.opensilver.net/download.aspx

この記事ではふれませんが、前述の CSHTML5 用のプロジェクトテンプレートも用意されています。同様に `拡張機能の管理` からインストールするか、[CSHTML5 のダウンロードページ](http://www.cshtml5.com/download.aspx)からインストーラーをダウンロードしてインストールすることもできます。

http://www.cshtml5.com/download.aspx

# プロジェクトの作成とプロジェクトの構成

プロジェクトテンプレートのインストールが完了すると、新規プロジェクトで OpenSilver Application が選択できるようになっているはずなのでこれを選択してプロジェクトを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/7b371427-063c-c115-160f-8210a7a4b490.png)
途中利用する .NET のバージョンを選択するダイアログが表示されるので、好きなバージョンを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/36c2f4dd-4688-a1f6-ca37-74896ae4b052.png)

ソリューションエクスプローラーを確認すると、作成したプロジェクトは次のような構成になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/e95740d6-fed3-da1c-a839-183fdcd29a5b.png)

この状態でデバック実行すると[このページ](https://www.opensilver.net/permalinks/wrong_startup_project.aspx)が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/82bdaabe-b946-f1c6-12bb-5ac691341e9c.png)

OpenSilver のプロジェクトテンプレートで作成される 3 つのプロジェクトは下記のような役割になっています。

| #   | プロジェクト                     | 用途                                                               |
| --- | -------------------------------- | ------------------------------------------------------------------ |
| 1   | OpenSilverApplication1           | OpenSilver のプロジェクト本体です                                  |
| 2   | OpenSilverApplication1.Browser   | OpenSilver をホストする ASP.NET Core のプロジェクトです。          |
| 3   | OpenSilverApplication1.Simulator | OpenSilver を Visual Studio でデバックするためのプロジェクトです。 |

Visual Studio で OpenSilver 本体を開発したりデバックしたりする場合は、OpenSilverApplication1.Simulator をスタートアッププロジェクトにしてデバック実行します。Web サイトにホストされた状態で動作を確認したい場合は、OpenSilverApplication1.Browser で動作を確認します。ただしこの場合は OpenSilver のプロジェクトに対し、ブレークポイントやウォッチ式などを使ったデバックは行えないので注意してください。

OpenSilverApplication1.Browser をスタートアッププロジェクトとして設定してデバックを開始すると次のようなページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/fcbaab39-7e7a-46d8-9829-e51763bc9988.png)

# OpenSilver は .NET Standard 2.0 相当

OpenSilverApplication1 プロジェクトは.NET Standard 2.0 のクラスライブラリプロジェクトとして作成されます。これは、OpenSilverApplication1.Simulator が .NET Framework ベースで動いているため.NET Standard のバージョンを低く設定せざるを得ないからです。

デバック機能を利用しないのであれば .NET Standard 2.1 に変更することもできますが、Visual Studio の強力なデバック支援が受けられなくなります。今後は変更があるかもしれませんが残念ながら現時点では .NET Standard 2.0 (C# 7.x) で開発せざるを得ません。

# .NET のライブラリを参照して MVVM してみる

基本的には XAML のアプリがそのままブラウザーで動くはずなので、WPF のアプリを開発するのりで実装出来るか確認して見ましょう。

OpenSilverApplication1 プロジェクトに必要なライブラリをインストールして、、、

```
PM> Install-Package Microsoft.Extensions.DependencyInjection
PM> Install-Package Microsoft.Extensions.Http
PM> Install-Package Microsoft.Toolkit.Mvvm
```

ViewModel を作って、、、

```cs:OpenSilverApplication1/ViewModels/MainPageViewModel.cs
public class MainViewModel: ObservableObject
{
    private int _counter;
    public int Counter
    {
        get => _counter;
        private set => SetProperty(ref _counter, value);
    }
    public ICommand IncrementCounterCommand { get; }
    public MainViewModel()
    {
        IncrementCounterCommand = new RelayCommand(() => Counter++);
    }
}
```

XAML を作って、、、

```xml:OpenSilverApplication1/Views/MainPage.xaml
<Page
    x:Class="OpenSilverApplication1.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:viewModels="clr-namespace:OpenSilverApplication1.ViewModels"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.DataContext>
        <viewModels:MainPageViewModel/>
    </Page.DataContext>

    <StackPanel>
        <TextBox Text="{Binding Counter, Mode=TwoWay}"/>
        <Button
            Content="Click me!"
            Command="{Binding IncrementCounterCommand}"/>
    </StackPanel>
</Page>
```

実行してみます。今回はデバックもしたいので、スタートアッププロジェクトは OpenSilverApplication1.Simulator に変更しました。ボタンをクリックすると RelayCommand 経由でテキストボックスの値が更新されるのを確認できます。

Simulator でデバックを開始すると、ブレークポイントやウォッチ式などのコード上のデバック機能の他、XAML のインスペクターや、携帯での表示確認などを行うこともできます。
Blazor WebAssembly 単体ではデバックが思うようにできなかったので、これはだいぶうれしいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/c5ff2a78-4a3c-2d0d-28d6-9039278110f4.png)

# DI を有効にして、外部サービスから値を取得してみる

次にもう少し実用寄りで、DI コンテナを定義し必要なサービスをコンストラクタインジェクションでもらいながら、外部サービスから値を取得してみます。

DI を設定してページのインスタンスをコンテナから取得するようにして、、、

```cs:OpneSilverApplication1/App.xaml.cs
public sealed partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();

        InitializeComponent();
        Window.Current.Content = Services.GetService<Views.MainPage>();
    }

    public new static App Current => (App)Application.Current;

    public IServiceProvider Services { get; }

    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();
        services.AddHttpClient();

        services.AddTransient<Views.MainPage>();
        services.AddTransient<MainViewModel>();
        return services.BuildServiceProvider();
    }
}
```

OpenSilver のアプリで気象庁から東京の今日の天気予報を取得してみます。

```cs:OpenSilverApplication1/ViewModels/MainPageViewModel.cs
public class MainViewModel: ObservableObject
{
    private string _weather;
    public string Weather
    {
        get => _weather;
        set => SetProperty(ref _weather, value);
    }
    public ICommand GetWeatherCommand { get; }

    public MainViewModel(HttpClient httpClient)
    {
        GetWeatherCommand = new AsyncRelayCommand(async () =>
        {
            var response = await
                httpClient.GetAsync(
                    new Uri("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json"));
            var content = await response.Content.ReadAsStringAsync();
            var result = JsonSerializer.Deserialize<ForecastResult>(content, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            });
            Weather = result.Text;
        });
    }
}
```

ボタンをクリックして天気情報を取得できましたね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/6bd467bb-13d9-7db9-faa4-02b95a1478c9.png)

# おわりに

Blazor や UnoPlatform がある中なので真っ先に名前が挙がるということはないかもしれませんが、既存の Silverlight 資産をどうにかマイグレーションしたいという場合には候補に上がるかもしれませんね。

今回使ったサンプルはここにおいておきます。

https://github.com/karuakun/SandBox/tree/master/OpenSilverApplication1