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

Blazorで、電卓アプリをつくってみる

新人研修や、新しいフレームワークが登場したとき、Hello Worldと並び、電卓アプリを作る人も多いのではないでしょうか。
今回はBlazor Client-Sideアプリで、簡単な電卓アプリを作り、PWA化してみたいと思います。

Demo
1.JPG

開発環境

Windows10
.Net Core 3.1
Visual Studio2019 Preview 16.5.0

電卓画面の実装

電卓画面はシンプルに1つのページなので、Index.razor に実装します。電卓アプリの計算はチョンボしてSystem.Data.DataTableに任せ、画面は数式と結果を表示するようにします。

Index.razor
@page "/"

<style>
    .calbtn {
        height: 45px;
        font-size: 30px;
        border: solid 1px;
        text-align: center;
    }

    .num {
        height: 45px;
        font-size: 30px;
        border: solid 1px;
        text-align: right;
    }
</style>
<div class="container-fluid">

    @*式を表示*@
    <div class="row">
        <div class="col-12 rounded-lg calbtn ">@str</div>
    </div>
    @*結果を表示*@
    <div class="row">
        <div class="col-12 rounded-lg num">@result</div>
    </div>

    @*ボタン類*@
    <div class="row">
        <div class="col-3 rounded-lg calbtn " @onclick="allclear">AC</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="(")">(</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+=")")">)</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="/")">/</div>
    </div>
    <div class="row">
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="7")">7</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="8")">8</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="9")">9</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="*")">*</div>
    </div>
    <div class="row">
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="4")">4</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="5")">5</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="6")">6</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="-")">-</div>
    </div>
    <div class="row">
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="1")">1</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="2")">2</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="3")">3</div>
        <div class="col-3 rounded-lg calbtn " @onclick="@(_=>str+="+")">+</div>
    </div>
    <div class="row">
        <div class="col-6 rounded-lg calbtn" @onclick="@(_=>str+="0")">0</div>
        <div class="col-3 rounded-lg calbtn" @onclick="@(_=>str+=".")">.</div>
        <div class="col-3 rounded-lg calbtn" @onclick="calc">=</div>
    </div>
</div>
@code{

    /// <summary>
    /// 数式
    /// </summary>
    string str = "";

    /// <summary>
    /// 結果
    /// </summary>
    string result = "0";

    /// <summary>
    /// ACボタン
    /// </summary>
    void allclear()
    {
        str = "";
        result = "0";
    }

    /// <summary>
    /// 計算処理
    /// </summary>
    void calc()
    {
        var dt = new System.Data.DataTable();
        try
        {
            result = dt.Compute(str.Trim(), "").ToString();
        }
        catch
        {
            result = "Error!!";
        }
    }
}

MainLayout.razorの編集

デフォルトのMainLayout.razorでは、アプリバーやサイドバーが表示されてしまうので、メインコンテンツのみ表示されるように編集します。

MainLayout.razor
@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>

PWA化

Blazor Client-Sideアプリでは、はじめからPWA(Progressive Web Apps)に対応していませんが、PWA化する方法はいくつかあります。今回は必要なファイルをwwwrootフォルダに配置して対応していきたいと思います。

manifest.jsonの作成

PWA化するには、manifest.jsonを用意する必要があります。ファイルを用意し、wwwroot直下に配置します。このファイルに誤りがあると、ブラウザにPWAアプリケーションとして正常に認識されないので、はまりどころとなります。

manifest.json
{
  "name": "Calculator",
  "short_name": "Calc",
  "start_url": "/",
  "theme_color": "#000000",
  "background_color": "#FFFFFF",
  "display": "standalone",
  "orientation": "portrait",
  "icons": [
    {
      "src": "images/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

URLの入力は不要なので、displayにはstandaloneを指定し、縦の表示にしたいのでorientationportraitに指定します。

serviceworker.jsの作成

オフラインで実行させるために、serviceworker.jsを用意します。ファイルを用意し、wwwroot直下に配置します。
こちらのサイトを参考に、serviceworker.jsを作成します。

Create Progressive Web Apps with .NET using Blazor

serviceworker.jsは、初回ロード時にオフライン実行用にキャッシュを作成するため、キャッシュさせたいファイルを列挙しています。面倒くさいですが誤りがあると、オフラインで実行できないので注意が必要です。

serviceworker.js
console.log("This is service worker talking");
var cacheName = 'blazor-pwa-sample';
var filesToCache = [
    //wwwroot
    '/',
    '/index.html',
    '/manifest.json',
    '/serviceworker.js',
    '/favicon.ico',
    //css
    '/css/site.css',
    '/css/bootstrap/bootstrap.min.css',
    '/css/open-iconic/font/css/open-iconic-bootstrap.min.css',
    '/css/open-iconic/font/fonts/open-iconic.woff',
    //images
    '/images/icon-192x192.png',
    '/images/icon-512x512.png',
    // カレントプロジェクトのdll
    '/_framework/_bin/BlazorApp40.dll',
    //.net framework
    '/_framework/blazor.webassembly.js',
    '/_framework/blazor.boot.json',
    '/_framework/wasm/mono.js',
    '/_framework/wasm/mono.wasm',
    '/_framework/_bin/Microsoft.AspNetCore.Authorization.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Blazor.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Blazor.HttpClient.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Components.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Components.Forms.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Components.Web.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Metadata.dll',
    '/_framework/_bin/Microsoft.Bcl.AsyncInterfaces.dll',
    '/_framework/_bin/Microsoft.Extensions.DependencyInjection.Abstractions.dll',
    '/_framework/_bin/Microsoft.Extensions.DependencyInjection.dll',
    '/_framework/_bin/Microsoft.Extensions.Logging.Abstractions.dll',
    '/_framework/_bin/Microsoft.Extensions.Options.dll',
    '/_framework/_bin/Microsoft.Extensions.Primitives.dll',
    '/_framework/_bin/Microsoft.JSInterop.dll',
    '/_framework/_bin/Mono.Security.dll',
    '/_framework/_bin/Mono.WebAssembly.Interop.dll',
    '/_framework/_bin/mscorlib.dll',
    '/_framework/_bin/System.ComponentModel.DataAnnotations.dll',
    '/_framework/_bin/System.Core.dll',
    '/_framework/_bin/System.Data.dll',
    '/_framework/_bin/System.dll',
    '/_framework/_bin/System.Net.Http.dll',
    '/_framework/_bin/System.Numerics.dll',
    '/_framework/_bin/System.Runtime.CompilerServices.Unsafe.dll',
    '/_framework/_bin/System.Text.Encodings.Web.dll',
    '/_framework/_bin/System.Text.Json.dll',
    '/_framework/_bin/System.Xml.dll',
    '/_framework/_bin/WebAssembly.Bindings.dll',
    '/_framework/_bin/WebAssembly.Net.Http.dll'
];

self.addEventListener('install', function (e) {
    console.log('[ServiceWorker] Install');
    e.waitUntil(
        caches.open(cacheName).then(function (cache) {
            console.log('[ServiceWorker] Caching app shell');
            return cache.addAll(filesToCache);
        })
    );
});

self.addEventListener('activate', event => {
    event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request, { ignoreSearch: true }).then(response => {
            return response || fetch(event.request).then(response => {
                caches.put(event.request, response.clone());
                return response;
            });
        })
    );
});

index.htmlの編集

wwwroot/index.htmlを編集し、manifest.jsonの読み込みの追加と、serviceworker.jsが動作するようにセットします。
今回は、iPhoneにインストールしたいため、いくつかの設定値を追加してセットしています。

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <!--user-scalable=n を追加して、拡大縮小を禁止する-->
    <meta name="viewport" content="width=device-width, user-scalable=n" />
    <title>Calculator</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />

    <!--追加-->
    <link rel="manifest" href="manifest.json" />
    <!--For iPhone/iPad-->
    <link rel="apple-touch-icon" href="images/icon-192x192.png" sizes="192x192" />
</head>

<body>
    <app>Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>

    <!--追加-->
    <script>
        if ('serviceWorker' in navigator) {
            console.log('Registering service worker now');
            navigator.serviceWorker.register('/serviceworker.js')
                .then(function () {
                    console.log('Service Worker Registered');
                });
        }
    </script>
</body>

</html>

動作確認

PWAとして正常に認識されると、様々なプラットフォームにインストールできるようになります。
4.JPG

オフラインでもキャッシュにより、動作が確認できます。

image.png

PWAといっても、実態はWEBブラウザですが、BlazorによりC#でiPhoneやmacOSで動くアプリケーションを作る事ができます。

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
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