かずきのBlog@hatena

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

Blazor で Hello world してみた

Blazor 気になってます。最初出てきた時に、これはいいものなのでは?と思ったけど実験的プロジェクトだったので触らなかったのですが、.NET Core 3.0 で入るので触ろうと思います。

実験的な段階の初期からあったブラウザーの WebAssembly で動くモード以外にもサーバーサイド Blazor というのがあって、こっちがどうも今のところ推奨っぽいです。サーバーサイド Blazor はサーバー側で処理やってクライアントサイドには SignalR を使って更新内容とかを伝えてるみたい。

なるほど、これなら初回起動時に各種 dll のダウンロードが走って起動が重いという問題も起きませんね。

準備

VS2019 と .NET Core 3.0 preview と Blazor 拡張機能を入れましょう。

marketplace.visualstudio.com

Blazor の拡張機能を入れると ASP.NET Core Web application を作成するウィザードで ASP.NET Core 3.0 を選ぶと Blazor 関係のテンプレートが追加されます。

f:id:okazuki:20190611125208p:plain

作ってみよう

Blazor (server-side) を選択して作成しました。Startup.cs を見てみると AddServerSideBlazor とか MapBlazorHub とか、それっぽいのが追加されています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using HelloBlazor.Data;

namespace HelloBlazor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

何も変更せず実行してみると動きます。

f:id:okazuki:20190611125932p:plain

Startup.cs を見てみるに、初期ページは Pages/_Host.cshtml_Host.cshtml は普通の Razor 構文で書かれたページで App をレンダリングしているようです。

App は恐らく App.razor で開いてみると、なんじゃこりゃってなりました。

@*
    The Router component displays whichever component has a @page
    directive matching the current URI.
*@
<Router AppAssembly="typeof(Startup).Assembly" />

雰囲気を察するに、Pages フォルダー以下の @page のついてるものを、その URL で出してくれるものと見受けられる。ルーティングの仕組み持ちってことで SPA 作成が捗りそう。

ハローワールド

じゃぁハローワールドします。心機一転で ASP.NET Core 3.0 で空のプロジェクトを作ります。 Startup.cs を開いて ConfigureServices メソッドを以下のようにします。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
}

見ての通り RazorPages と ServerSideBlazor の有効化です。 続けて Configure メソッドの UseEndpoints の部分にも Blazor を追加しておきます。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStaticFiles();
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
    });
}

Pages フォルダーを作って、そこに _Host.cshtml を作ります。とりあえず余計なものはつかないようにチェック全部外しました。

f:id:okazuki:20190611131937p:plain

なんか、EntityFramework とか追加されてしまった…とりあえず気を取り直して以下のように編集しました。

@page "/"
@namespace  HelloWorldBlazor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>_Host</title>
</head>
<body>
    <app>@(await Html.RenderComponentAsync<App>())</app>

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

この RenderComponentAsync の App から Blazor の世界なのかな?あと、_framework/blazor.server.js を参照するために UseStaticFiles が Startup.cs に必要だったみたいです。

次は App を作っていきます。プロジェクト直下に App.razor というファイルを作ります。特にテンプレートは無いっぽいのでテキストファイルとして作りました。

何も考えずにハローワールドって書きます。

<h1>Hello world</h1>

実行すると出ました。

f:id:okazuki:20190611133539p:plain

ちゃんと Blazor なのかこれだとわかりにくいので @functions を足してみました。

<h1>Hello world</h1>

<p>@Count</p>
<button onclick="@Increment">Increment</button>

@functions {
    int Count { get; set; }

    void Increment() => Count++;
}

動いた。

f:id:okazuki:20190611134737p:plain

ルーティング

せっかくなんてルーティングしてみます。App.razor を以下のように変更します。

<Microsoft.AspNetCore.Components.Routing.Router AppAssembly="typeof(App).Assembly" />

プロジェクトテンプレートで生成したやつは単に Router タグでしたが、これは _Imports.razor にあらかじめ using が書かれてるから省略できてるって類のものっぽいですね。

そして Pages に Index.razor と Next.razor を作ります。以下のように編集しました。

Index.razor

@page "/"

<h1>Index</h1>

<a href="/Next">Next</a>

Next.razor

@page "/Next"

<h1>Next</h1>

実行してみると…思った通りに動きました。

f:id:okazuki:20190611135438g:plain

まとめ

大してドキュメントを読まなくても ASP.NET Core MVC とかで Razor を軽く触ったことがあれば、@functions { ... } の下に C# のクラスの中身を書いていくという雰囲気くらいの理解で上記くらいのことは簡単に出来ました。

雰囲気つかめたので、今度は色々ドキュメントを読み漁ってみよう。