ASP.NET Core Blazor WebAssembly のハローワールド

Blazor WebAssembly を試してみようかなと思い立ったので以下のドキュメントを写経してみました。

[ASP.NET Core Blazor の概要](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/get-started?view=aspnetcore-3.1&tabs=visual-studio)

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

今のところプレビューなので自前で入れます。.NET Core SDk 自体も 3.1.102 以降である必要があります。私は、現時点での最新版の `3.1.201` が入っていました。以下のコマンドでプロジェクトテンプレートをいれます。

```
dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview2.20160.5
```

入れると、Visual Studio のプロジェクトテンプレートの Blazor アプリの中に Blazor WebAssembly App が生えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/2f8a2d80-f591-bed3-fe04-b9fbedf07285.png)

右下の ASP.NET Core hosted にチェックを入れると、ASP.NET Core のプロジェクトも作れて、そこに WebAssembly も入れてデプロイ出来るので Azure WebApps とかにデプロイするのが楽そうなので、それのチェックを入れて作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/61b9aa53-8053-53aa-fe27-62baa46ea59e.png)

新規作成すると、以下のようなテンプレートが生成されます。
この段階で ASP.NET Core 側の API を叩いて画面に表示する例のコードまで入ってい
るのはありがたいですね。Server 側プロジェクトにある WetherForecastController が WebAPI です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/bc1ea744-c725-c30d-d115-425b03f44c97.png)

Client 側プロジェクトの Pages/FetchData.razor を見ると以下のように WetherForecast の URL 叩いています。いいね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/c46fae06-1753-0d2a-a688-b880fac4baee.png)

## ローカル実行

何も考えずにローカル実行をするとブラウザーが立ち上がって WebAssembly の Blazor が動きますね。完璧。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/444360d7-109c-d03d-ebaa-1b2f983a0f81.png)

## Azure にデプロイ

Azure WebApps にデプロイしてみましょう。デプロイするのは Server 側のプロジェクトです。右クリックから発行を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/47cf08c8-5e31-80a6-6808-6673b441ea12.png)

適当に発行先を選んで（もしくは新規作成）デプロイをすると、本当にすんなりと動きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/92a4da45-fc63-6b05-fe04-059abb3d79a1.png)

WebApp とかだと `https://サイト名.azurewebistes.net/` 直下に作られるので気にしなくていいのですが、そうではなくて `https://example.com/YourAppName/` のようなパスの下に展開されるときはアプリのベースパスの設定が必要なので、デプロイするときは、そこに気を付けましょう。

[アプリのベース パス](https://docs.microsoft.com/ja-jp/aspnet/core/host-and-deploy/blazor/?view=aspnetcore-3.1&tabs=visual-studio#app-base-path)

## 起動シーケンスを少し見てみる

プレビューなのでデプロイまで、もう少しハマると思ったら、何もハマらなかったのでちょっと拍子抜けしました。少し WebAssembly 上での起動時のシーケンスでも追ってみようと思います。

Client 側プロジェクトには Program.cs があります。ここにある Main メソッドがクライアントサイドの C# としてのエントリーポイントになるでしょう。

見てみると以下のような感じで WebAssmeblyHostBuilder を作ってルートのコンポーネントの登録をしたり、Http 呼び出しに使う HttpClient クラスの登録をしてから実行してるように見えます。

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace HelloBlazor.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddBaseAddressHttpClient();

            await builder.Build().RunAsync();
        }
    }
}
```


App クラスはどうなっているかというと、ただの App.razor ファイルです。

```html
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
```

`<Found Context="routeData">` の部分と `<RouteView RouteData="@routeData"` の routeData は、名前が一致していないといけないようですね。DefaultLayout は `typeof(MainLayout)` となっているので、`Shared/MainLayout.razor` がレイアウト定義に使われています。

```html
@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>
```

`@Body` でレイアウトファイルを適用したファイルの中身が展開される場所なんでしょうね。

App.razor の Router タグは自動的に `RouteAttribute` が適用されているもの（.razor ファイル内で `@page` が指定されているもの)を探してくれます。

例えば `Pages/Index.razor` を見ると `@page "/"` と書かれています。

```html
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />
```

これで、デフォルトの `https://example.com/` などのような URL にアクセスされたときに自動的に `Index.razor` に行きつくようになっています。

Program.cs の builder の Services に自前クラスを追加することで DI も動きますね。

例えばこんな感じ MyClass を追加して…

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace HelloBlazor.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddBaseAddressHttpClient();
            builder.Services.AddSingleton<MyClass>();

            await builder.Build().RunAsync();
        }
    }

    public class MyClass
    {
        public string Message => "Hello world";
    }
}
```

Index.razor などで使うには `@inject クラス名 変数名` のような行を追加したらインジェクション出来ます。

```html
@page "/"
@inject MyClass myClass

<h1>Hello, world!</h1>

<p>@myClass.Message</p>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />
```

FetchData.razor のように C# のコードを .razor の中に直接うめることは出来ますが、partial class を使って .cs ファイルに分離を出来ます。

[部分クラスのサポート](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/components?view=aspnetcore-3.1#partial-class-support)

## まとめ

結構いい感じに出来そうですね。HTML/CSS は好きだけど JavaScript が苦手な人にはいいかもしれない。WebAssembly 系の弱点の起動処理が重い点を除けば…。