Help us understand the problem. What is going on with this article?

Web開発でJavaScriptの代わりにC#でロジックを書くBlazorがめちゃ便利!

はじめに

UL Systems Advent Calendar 2019 の17日目です。

Blazorとは、.NET の技術を使ってクライアント側のWeb UIを構築するためのフレームワークです。このBlazorを使えば、今まで主に業務アプリケーションの開発で C#を使用してきたような開発者であっても、極力JavaScriptを使わず、追加の学習コスト少なくWebアプリケーション(SPA)を開発することができるようになります。

Blazor には、サーバ側で処理するBlazorサーバーと、クライアント側で処理するBlazor WebAssemblyがあります。Blazorの開発を今まさに進めており、2019年9月にリリースされた.Net Core 3.0にてBlazor サーバーが正式にサポートされるようになりました。Blazor WebAssemblyも2020年5月に正式リリースされる見通しです。
Blazor WebAssemblyは、webフロントエンドの高速化などを目的として、C、C++でコンパイルされた機械語をブラウザ上で実行できるWebAssemblyを使用していますが、先日WebAssemblyがW3C勧告に到達したことも発表され、事実上Webの標準になり、注目されています。

今回は、BlazorのメリットとBlazorの作成方法の触りをチュートリアルを通じてご紹介します。

Blazor のメリット

今までの.Netノウハウが活用できる

Blazor のメリットは、.NET ライブラリなど既存の .NETのノウハウを活用できることです。いつものVisual Studio、C#構文、NuGet、MSTestなどをそのままに開発することができます。

サーバーとクライアント全体でアプリケーションのロジックを共有できる

従来のサーバーと通信するアプリの場合、クライアントとサーバ間のやり取りをAPI経由で別々に実装していることが多いです。しかし、Blazorではクライアントとサーバーを一つのソリューションで作成できます。
例えば、型情報をDTOごと共有できるため、サーバ・クライアント間で型を合わせたり重複したコードを書くこともなくなります。

デバイスを選ばないクロスプラットフォーム

当たり前ですが、SPAなのでブラウザ上でブラウザが動く環境であれば基本的には同じように動作させることができます。現在、WebAssemblyは一般的に使用されるブラウザには実装済みです。
なお、プラグイン方式ではないので、その点ではSilverlightのような形で主要ブラウザで使えなくなってしまうようなことはなさそうです。

(Blazor WebAssemblyの場合、)常時接続環境を必須としない

Blazor WebAssemblyではオフラインのみでも実行可能であり、常時接続を保証できないような条件でも大丈夫です。
この場合、PWA化も選択肢に入るかと思います。

Blazor の始め方

Visual Studio 2019 最新版を持っていれば、Blazor拡張を別途インストールすることなくBlazorのプロジェクトを作成することができます。
なお、Blazorプロジェクトのプレビュー版のテンプレートを選択するためには下記のコマンドを打つ必要があります。
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.1.0-preview4.19579.2

上記のコマンドにより、下記のテンプレートが選択できるようになります。
blazor_template.png

これを選択すると下記のように、数字をカウントアップするアプリと、取得したデータを表示する機能を持つアプリが出来上がっています。下記は取得したデータを表示するものです。
image.png

なお、以降では、Blazor WebAssemblyについて記載します。

Blazorプロジェクトの構造

Blazor のプロジェクトの中で作成が必要なファイルには主に以下があります。

  • Program.cs :ASP.NET Coreホストを設定するアプリのエントリポイント。定型で変更されることは殆どありません。
  • Startup.cs :アプリのスタート時に実行されるロジックです。下記のようにアプリ全体で使用する設定が記述されます。
    • アプリの依存関係挿入 (DI)サービス
    • アプリの要求処理パイプライン
  • wwwroot/index.html: Blazorそのものを起動するための情報が書かれています。META要素などのヘッダー情報を変更する必要が発生したら書き換えることになります。
  • [ページ] フォルダー(Razorファイル):アプリ上のページをコーディングします。
    • 各ページの名前は @page を使用して指定します。
    • HTML構文の中に @code で括ってC#のコードを埋め込むことができます。

チュートリアルをもとに実践する

Github上でピザの注文ウェブアプリが題材のワークショップが公開されています。
チュートリアルとしては、それなりのボリュームです。

https://github.com/dotnet-presentations/blazor-workshop

今回はこの中から、Section1, Section2を題材に、主要なコードを確認してみたいと思います。
この2つのセクションでは、ピザの注文のうち、クライアント側でピザを表示するところから注文を入力するところまでが実装されます。

なお、Blazorのプロジェクト作成の指針としては、下記のようにしていくのが望ましそうです。このワークショップで作成されるコードもこのようなプロジェクト構成になっています。

  • サーバー側の処理はServer側のプロジェクトに実装。基本的には、表示に必要なデータを構成することのみをサーバー側の責務とする。
  • レンダリング(HTML自体の構成)は主にClient側のプロジェクトに機能を実装。
  • jsonで通信するクラスやサーバーとクライアントの両方で使用するユーティリティをSharedのプロジェクトに実装。

Section1 Components and Layout

このSectionでは、「クライアント側の表示」処理の実装を作成しています。このSectionが完成すると以下のような画面が表示されます。

image.png

以下に、このSectionで作成されるコードの一部を示します。表示ロジックは.razor の拡張子のファイルにrazor構文という形で記述します。

razor構文の中で主に実装している内容は以下です。HTMLとC#の構文を知っている人であれば、それほど迷いなく理解できるのではないかと思います。

  • @code の中で画面に表示するためのオブジェクトの中身(specials)をセットして、HTMLの中に埋め込んだ変数で表示するようにしています。
  • @code のなかで使用しているHTTPClient は、@inject をページで宣言することで使えるようになります。HTTPClient はDIの宣言をせずにデフォルトで使用することができます。
  • サーバ側で宣言された"specials" APIからピザのデータを取得し、そのデータをビューに表示するようにしています。サーバ側に取得用の実装がありますがその説明は省略します(c#でDBからデータを取得するロジックが書かれています)
Pages/Index.razor
@page "/"
@inject HttpClient HttpClient
<div class="main">
    <ul class="pizza-cards">
        @if (specials != null)
        {
            @foreach (var special in specials)
            {
                <li style="background-image: url('@special.ImageUrl')">
                    <div class="pizza-info">
                        <span class="title">@special.Name</span>
                        @special.Description
                        <span class="price">@special.GetFormattedBasePrice()</span>
                    </div>
                </li>
            }
        }
    </ul>
</div>

@code {
    List<PizzaSpecial> specials;

    protected async override Task OnInitializedAsync()
    {
        specials = await HttpClient.GetJsonAsync<List<PizzaSpecial>>("specials");
    }
}

Section2 Customize a pizza

ここでは、クライアントでピザを選択し、ダイアログでトッピング等を選択したものの送信を作成します。
このSectionが完成すると、ピザをクリックして以下のようなダイアログが表示され、注文を送信することができます。

image.png

以下の説明ではセクションの内容全てではなく一部の説明を省いています。

Implement the pizza customization dialog

以下はピザが選択された後に、ダイアログを表示するイベントの実装です。
ダイアログが閉じられた後のイベントでダイアログが閉じられるよう、showingConfigureDialogのフラグでダイアログ起動を制御しています。

Pages/index.razor
<li @onclick="@(() => ShowConfigurePizzaDialog(special))" style="background-image: url('@special.ImageUrl')">  // ピザをクリックしたらShowConfigurePizzaDialogを呼び出す。
~~
@code {
    void ShowConfigurePizzaDialog (PizzaSpecial special)
    {
        configuringPizza = new Pizza()
        {
            Special = special,
            SpecialId = special.Id,
            Size = Pizza.DefaultSize,
            Toppings = new List<PizzaTopping>(),
        };

        showingConfigureDialog = true;
    }
}
@if (showingConfigureDialog)  // showingConfigureDialog がtrueになったらダイアログを表示、falseで非表示にする
{
    <ConfigurePizzaDialog Pizza="configuringPizza" ~~>
}

Data binding・Add additional toppings

ダイアログ側の処理です。以下の処理では、レンジ入力から数を設定するものと、リストボックスから値を選択する実装の一部です。レンジ入力のようにレンジバーが変わった都度値がデータに反映されるようにするために @bind をつけています。リストボックスの選択時は、メソッドを呼び出して値を入れています。

Shared/ConfigurePizzaDialog.razor
        <input type="range" min="@Pizza.MinimumSize" max="@Pizza.MaximumSize" step="1" @bind="Pizza.Size" @bind:event="oninput" />
~~
        <select class="custom-select" @onchange="@ToppingSelected">
            <option value="-1" disabled selected>(select)</option>
            ~~
        </select>
~~
    void ToppingSelected(ChangeEventArgs e)
    {
        if (int.TryParse((string)e.Value, out var index) && index >= 0)
        {
            AddTopping(toppings[index]);
        }
    }

    void AddTopping(Topping topping)
    {
        if (Pizza.Toppings.Find(pt => pt.Topping == topping) == null)
        {
            Pizza.Toppings.Add(new PizzaTopping() { Topping = topping });
        }
    }

Display the current order

最後に、選択された注文をサーバ側に送信する部分です。(orderという変数に注文内容が格納されていた後の処理です。

Pages/index.razor
async Task PlaceOrder()
{
    await HttpClient.PostJsonAsync("orders", order);
    order = new Order();
}

さいごに

c# でSPAが書けるBlazorを紹介しました。現段階でまだWebAssemblyはプレビュー版でまだ発展途上であることや、.Net技術者でwebアプリを書くことのニーズがどれほどあるか、またどの程度流行るのか未知数のところもあり、本番で使うことはまだ早いかもしれません。
しかし、このようなC#の技術を生かして、複雑なSPAを比較的学習コストの負担が少なく簡単に作れるBlazorは可能性のある技術だと思います。今後の動向にも注目です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした