echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【.net Core】Blazorアプリを作成する&Azureで公開する

概要

先日9/23に、ついに.net core(ver3.0)がメジャーリリースされました。(LTSはver3.1だそうですが)

そして、9/26(木)にマイクロソフトで開催された、下記イベントに参加してきました。

この中で、マイクロソフトの井上さん(チャックさん)が話された、.net Coreの概要について、下記が魅力的だな、と思いました。

  • .net CoreがWPF、及びWinFormsに対応
  • .net Coreメジャーリリースに合わせて、Blazorも正式対応

上記のうちBlazorについて簡単に触って、ついでにAzureに公開するところまでやりましたので、紹介がてら。

Blazorとは?

Blazorの概要&詳細については、マイクロソフト公式ページに書いてありますので、そちらを参照。

個人的に、上記イベントでのBlazorの印象は、

「Vue.jsの"単一ファイルコンポーネント"を、C#で実現した感じ」

でした。(「Vue.jsの単一ファイルコンポーネント」についてはググって下さい)

実際、一言で表現するなら、こんな感じになると思います。

プロジェクトを作成する

とりあえず、Visual Studioで、下記手順でBlazorプロジェクトを作成します。

  • Visual Studioで[新規作成] - [ファイル]を選択し、テンプレートから[Blazorアプリ]を選択
  • プロジェクト名などを設定し「作成」をクリック

プロジェクトを作成後、下記手順で新しいRazorコンポーネントを追加します。
- ソリューションエクスプローラ上で「Pages」フォルダを右クリック
- [追加] - [新しい項目] - [Razorコンポーネント]を選択

f:id:Makky12:20191004192648p:plain f:id:Makky12:20191004192700p:plain

プログラムを書く

とりあえず、サンプルで書いたのソースが下記。(クッソ長くなったな...)

razor記法についてはここでは突っ込みませんが、特徴は下記あたりでしょうか。

  • 「@page」でページのリンク(ルーティングパス)を定義
  • 「@code」にバックグラウンドロジックを記載する
    • Vue.jsでいう「<script>」タグ
  • HTML内に「@onclick」「@if」など、分かりやすいディレクティブが使える
    • Vue.jsで言う「v-on:click」や「v-if」
    • 下記ソースには「@onclick」はないですが、デフォルトで作成される「Pages/Counter.razor」に記載されています。

やたら「Vue.jsで言う」と言っていますが、このあたりが先程の

「Vue.jsの"単一ファイルコンポーネント"を、C#で実現した感じ」

な気がします。

// Pages/MyRazor.razor
@page "/myrazor"  
  
@using BlazorApp1.Data
@using System.Collections.Generic
@using System.Linq;
@using System.Net.Http;
@using Newtonsoft.Json;
@using System.Diagnostics  
  
<h1>MyFirstRazor</h1>

<div class="container">
    <div class="row">
        <div class="col-10 offset-1">
            <div class="btn-group btn-group-toggle" data-toggle="buttons">
                <label class="btn btn-primary active">
                    <input type="radio" name="options" id="option1" autocomplete="off" checked />全員
                </label>
                <label class="btn btn-primary">
                    <input type="radio" name="options" id="option2" autocomplete="off"  />日本人
                </label>
                <label class="btn btn-primary">
                    <input type="radio" name="options" id="option3" autocomplete="off" />外国人
                </label>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-10 offset-1">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th scope="col">ID</th>
                        <th scope="col">Name</th>
                        <th scope="col">Country</th>
                    </tr>
                </thead>
                <tbody>
                    @if (goalkeepers != null)
                    {
                        foreach (var gk in goalkeepers)
                        {
                            <tr>
                                <td>@gk.Id</td>
                                <td>@gk.Name</td>
                                <td>@gk.Country</td>
                            </tr>
                        }
                    }
                </tbody>
            </table>
        </div>
    </div>
</div>  
  
@code
{
    List<GoalKeeper> goalkeepers = null;
    List<GoalKeeper> allGoalKeepers = null;
  
    protected override async Task OnInitializedAsync()
    {
        var client = new HttpClient();
        var response = await client.GetStringAsync("(Azure Functionの呼び出し用URL)");
        allGoalKeepers = JsonConvert.DeserializeObject<List<GoalKeeper>>(response);
        goalkeepers = new List<GoalKeeper>(allGoalKeepers);
    }
  
    private EventCallback FilterGoalKeeper(int mode)
    {
        Debug.WriteLine(mode.ToString());
        List<GoalKeeper> gk = null;

        switch (mode)
        {
            case 0:
                gk = new List<GoalKeeper>(allGoalKeepers);
                break;
            case 1:
                gk = (List<GoalKeeper>)from item in allGoalKeepers
                                       where item.Country == "Japan"
                                       orderby item.Id
                                       select item;
                break;
            case 2:
                gk = (List<GoalKeeper>)from item in allGoalKeepers
                                       where item.Country != "Japan"
                                       orderby item.Id
                                       select item;
                break;
        }
    }
}  
  
  
// Data/GoalKeeper.cs  
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorApp1.Data
{
    public class GoalKeeper
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Country { get; set; }

        public GoalKeeper(int id, string name, string country)
        {
            this.Id = id;
            this.Name = name;
            this.Country = country;
        }
    }
}
Bootstrapの適用

また、HTML部分を見て気づいた人もいるでしょうが、上記HTMLにはBootstrap4を適用してます。

Blazorでも、下記パッケージをNuGet経由でインストールすれば、Bootstrapを使用出来ます。

インストール後、Pages/_Host.cshtmlファイルの「body」タグに下記を追加するだけでOKです。

<body>
    <!-- この1行を追加-->
    <script src="_content/BlazorStrap/blazorStrap.js"></script>
</body>

Azureで動かす

で、クライアント部分は完成したので、せっかくなのでAzureにデプロイして、Azure上で動かします。

デプロイ方法ですが、下記方法でOKです。

  • ソリューションエクスプローラでプロジェクトを右クリックして「発行」を選択
  • 「発行先を選択」で「Azure App Service」を選択
  • 「App Service 新規作成」で、下記画像の各種情報を指定し、「作成」をクリックする。
    • 「リソースグループ」「ホスティングプラン」は、その場で新規作成ができます。
    • デフォルトだとUSリージョンが選ばれている場合があるので注意。

※もし「発行先」で「WebサーバーかAzurか」という選択が出た場合は「Azure」を選択してください。
(一度だけ出たのですが、再現しなかった...)

f:id:Makky12:20191004193752p:plain f:id:Makky12:20191004193802p:plain

ただ、Azureにデプロイ完了しても、このままでは動きません。
Azureがまだ.net Core3.0に対応していないようなので、.net Core3.0が動くようにする必要があります。

それには、下記手順を実行すればOKです。

  • デプロイしたBlazorの「App Service」リソースで、「拡張機能」を選択する
  • ASP.NET Core 3.0 (x64) Runtime」をインストールする
  • 「設定」→「構成」をクリックし、「全般設定」タグで以下の設定をする。
    • スタック:.net core
    • プラットフォーム:64bit

f:id:Makky12:20191004194003p:plain f:id:Makky12:20191004194014p:plain

これでデプロイしたBlazorアプリの「概要」に表示されているURLを表示すれば、Azure上でBlazorアプリが動くはずです。

f:id:Makky12:20191004195255p:plain

※なお、デプロイ&再デプロイ時に下記ダイアログが表示されますが、これは先程の「Azureがまだ.net Core3.0に対応していない」のが理由と思われるので、とりあえずは気にしなくてOKです。

f:id:Makky12:20191004195436p:plain

ちなみに、Azure Functionのソースはこちら。

using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
  
namespace Company.Function
{
    public static class HttpTriggerCSharp
    {
        [FunctionName("HttpTriggerCSharp")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");
  
            var gk = new List<GoalKeeper>();
            gk.Add(new GoalKeeper(1, "ハーフナー・ディド", "Netherland"));
            gk.Add(new GoalKeeper(2, "伊藤 裕二", "Japan"));
            gk.Add(new GoalKeeper(3, "楢崎正剛", "Japan"));
            gk.Add(new GoalKeeper(4, "川島永嗣", "Japan"));
            gk.Add(new GoalKeeper(5, "ミチェル・ランゲラク", "Australia"));
  
            string resopnseJson = JsonConvert.SerializeObject(gk);
            return (ActionResult)new OkObjectResult(resopnseJson);
        }
    }
  
    class GoalKeeper
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Country { get; set; }

        public GoalKeeper(int id, string name, string country) {
            this.Id = id;
            this.Name = name;
            this.Country = country;
        }
    }
}

まとめ

と、かなり駆け足でしたが、Blazorについて、簡単に紹介しました。

個人的にマイクロソフトは、環境やプラットフォームはかなりシェア持ってるけど、Web系言語としての採用率がJS系フレームワークPHPなどに比べてイマイチ...と思っていただけに、巻き返しに期待したいところです。