かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

クライアントサイド Blazor してみよう

プロジェクト作って眺めてみようと思います。

.NET Core 3.0 Preview 6 + Visual Studio 2019 Preview + Blazor 拡張機能で試しています。

.NET Core 3.0 Preview 6 で、@functions から @code に変わったりと細かい変更がちょくちょくありました。さらには認証にも対応(個人的にこれ嬉しい)したみたいで、なんだか本格的に必要な機能セットが揃ってき始めてる気がします。

詳細は以下のページで。

devblogs.microsoft.com

さて、クライアントサイドの Blazor といっても 2 種類のプロジェクトがあって Blazor (ASP.NET Core hosted) と Blazor (client-side) があります。デプロイのハードルが低そうなのがとりあえず ASP.NET Core hosted なので、そっちから行ってみようと思います。

新規作成すると 3 つのプロジェクトが作られます。

f:id:okazuki:20190613172024p:plain

上から Blazor のプロジェクト(.NET Standard 2.0)、ASP.NET Coreの プロジェクト(.NET Core 3.0)、共有したいクラスを入れるプロジェクト(.NET Standard 2.0)になります。

実行すると Blazor のテンプレートには Hello world のページとボタンを押すとカウントアップするページと表形式でデータを表示するページがあるアプリが立ち上がります。サーバーサイド Blazor と大きく異なるのは、ちゃんと REST API を叩いてるところです。 ASP.NET Core のほうのプロジェクトの Controllers フォルダーを見ると REST API が作られてます。

そして、クライアント側のプロジェクトの Pages/FetchData.razor を見るとちゃんと REST API を叩いています。

WeatherForecast[] forecasts;

protected override async Task OnInitAsync()
{
    forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}

この Http はページの上部で @inject HttpClient Http という形で定義されていて、DI で HttpClient をもらってます。いいね。

サーバーのスタートアップロジック

サーバーのほうの Startup.cs を覗いてみると Blazor に関わりそうな処理が Configure メソッドにいくつかあります。

app.UseResponseCompression();

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseBlazorDebugging();
}

app.UseClientSideBlazorFiles<Client.Startup>();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapDefaultControllerRoute();
    endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});

恐らく UseResponseCompression で、レスポンスの圧縮してね♪的なことが以下のドキュメントに書いてあるので、そこらへんのためだと思います。

docs.microsoft.com

あとは、UseClientSideBlazorFiles でクライアントサイドの Blazor のファイルを指定して、MapFallbackToClientSideBlazor で クライアント側のプロジェクトの wwwroot/index.html に処理を回すようになってるように見受けられます。

index.html は以下のようになっていて blazor.webassembly.js を読み込んでいます。このファイルをおしゃれにすればアプリが立ち上がるまでの間のユーザーのイライラを多少緩和出来るっぽい?

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>ClientSideBlazorHelloWorld</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>Loading...</app>

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

ここで app タグを指定していて、この app タグはクライアントサイドの Startup.cs で AddComponent の部分で App クラス(ファイルとしては App.razor)であるということがわかります。

using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace ClientSideBlazorHelloWorld.Client
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IComponentsApplicationBuilder app)
        {
            app.AddComponent<App>("app");
        }
    }
}

App.razor を見ると Blazor のルーティングの定型文が書いてあります。

<Router AppAssembly="typeof(Program).Assembly">
    <NotFoundContent>
        <p>Sorry, there's nothing at this address.</p>
    </NotFoundContent>
</Router>

Router は、_Imports.razor で自動的に using されている名前空間の中にあるクラスです。

@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Layouts
@using Microsoft.AspNetCore.Components.Routing // これ!
@using Microsoft.JSInterop
@using ClientSideBlazorHelloWorld.Client
@using ClientSideBlazorHelloWorld.Client.Shared

そして、Pages の中の各種ページにルーティングされて、初期状態のパスの / に紐づくのは Index.razor なので、 Index.razor の中身が表示されます。Pages フォルダーの中の _Imports.razor では @layout MainLayout だけが書いてあり、ここでこのフォルダーの中身は基本的に MainLayout.razor をレイアウトファイルとして使うことがわかります。

MainLayout.razor は Shared フォルダーにあって以下のようになっています。

@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 の部分に今回は Index.razor が入る感じですね。NavMenu は NavMenu.razor にありますが、こちらは NavLink (これは Blazor 側で提供されてるクラス)を使ってメニュー作ってるだけです。fetchdata, counter などへの遷移するためのメニューです。

まとめ

とりあえずさらっと眺めてみました。 理解あってるかなぁ。