新人研修や、新しいフレームワークが登場したとき、Hello World
と並び、電卓アプリを作る人も多いのではないでしょうか。
今回はBlazor Client-Sideアプリで、簡単な電卓アプリを作り、PWA化してみたいと思います。
開発環境
Windows10
.Net Core 3.1
Visual Studio2019 Preview 16.5.0
電卓画面の実装
電卓画面はシンプルに1つのページなので、Index.razor
に実装します。電卓アプリの計算はチョンボしてSystem.Data.DataTable
に任せ、画面は数式と結果を表示するようにします。
@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
では、アプリバーやサイドバーが表示されてしまうので、メインコンテンツのみ表示されるように編集します。
@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アプリケーションとして正常に認識されないので、はまりどころとなります。
{
"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
を指定し、縦の表示にしたいのでorientation
をportrait
に指定します。
serviceworker.jsの作成
オフラインで実行させるために、serviceworker.js
を用意します。ファイルを用意し、wwwroot
直下に配置します。
こちらのサイトを参考に、serviceworker.js
を作成します。
Create Progressive Web Apps with .NET using Blazor
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にインストールしたいため、いくつかの設定値を追加してセットしています。
<!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として正常に認識されると、様々なプラットフォームにインストールできるようになります。
オフラインでもキャッシュにより、動作が確認できます。
PWAといっても、実態はWEBブラウザですが、BlazorによりC#でiPhoneやmacOSで動くアプリケーションを作る事ができます。