Blazor Server で実現できる最もシンプルな非同期処理のサンプルを作ったので参考にしてほしい。ソースコードはGitHubで公開しました。
※この作りをJavaScriptを書かずに簡単に実装できるのが、Blazor Server の生産性の高さの真髄。
ソースコード構成
Visual Studio プロジェクト の Blazor Server テンプレートに、LoadingOverlay.razor LoadingOverlay.razor.css を追加し、Counter.razor を少し修正したのみ。
ソースコード変更内容を解説
LoadingOverlay.razor
・パーツ化した「処理中…」画面。
・Visibleパラメータ変数で、「処理中…」画面<div>の表示/非表示を、if (Visible) で切り替えてる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@if (Visible) { <div id="loading"> <div class="w-100 d-flex justify-content-center align-items-center"> <div class="spinner-border text-primary spinner-custom" role="status"></div> </div> <strong><span>処理中です...</span></strong><br> </div> } @code { [Parameter] public bool Visible { get; set; } = false; } |
LoadingOverlay.razor.css
・「処理中…」画面パーツ専用のCSS。
・「z-index: 999991」で「処理中…」画面パーツを最前面に表示している。
・画面全体を覆う「処理中…」 を最前面に表示することで、処理実行中にボタンを連打されたり、処理が終わる前に他の処理が開始されるのを防げる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
.spinner-border.spinner-custom { width: 8rem; height: 8rem; border: 0.5em solid currentColor; border-right-color: transparent; } #loading { position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; background-color: black; z-index: 999991; -moz-opacity: 0.5; opacity: .50; filter: alpha(opacity = 50); text-align: center; color: #007bff; padding-top: 350px; } #loading span { font-size: 1.85em; color: #FFF; } |
Counter.razor
・「処理中…」画面パーツを使用しているサンプル画面。
・JsRuntime.InvokeAsync()を使い、一般的な動きとしての確認メッセージ/完了メッセージを、JavaScriptの confirm/alert で実装。
・<LoadingOverlay Visible=”@IsLoadingOverlay”> で「処理中…」画面パーツを組み込み、IsLoadingOverlay変数で「処理中…」画面の表示/非表示を切り替えている。
・IsLoadingOverlay変数の変化は StateHasChanged() を実行することでブラウザへ反映している。
・ボタンクリック時の、IncrementCount() イベントハンドラ内の処理は全て同期で実装していますが、処理が完了するまで「処理中…」画面を前面に表示することで、ユーザーから見た動きは非同期になっています。
・(重要)イベントハンドラ内の処理を全て、サービス化せずに非同期に実装できるということは、ソースコードの実装がとてもシンプルになるということ。
同期で実装できるということは、ビジネスロジックを全て static class で実装し、パフォーマンスを上げることが可能ということ。データはインスタンスクラスで各staticメソッドに流して行くイメージ。
・ ビジネスロジック() メソッドのように、Blazor Server の変数名、メソッド名には日本語を使える。日本人は日本語を多用してプログラムを書いた方が、色々とスムーズに進むと常々思う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> <LoadingOverlay Visible="@IsLoadingOverlay" /> @code { [Inject] private IJSRuntime JsRuntime { get; set; } private bool IsLoadingOverlay = false; private int currentCount = 0; private async Task IncrementCount() { var confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "開始しますか?"); if (!confirmed) { return; } IsLoadingOverlay = true; StateHasChanged(); await ビジネスロジック(); currentCount++; IsLoadingOverlay = false; StateHasChanged(); await JsRuntime.InvokeVoidAsync("alert", "完了しました。"); } /// <summary> /// ビジネスロジック、DB処理などは全てstaticで実装するとパフォーマンスが良い。 /// </summary> private static async Task ビジネスロジック() { // なんらかの重たい処理を実行 Start await Task.Delay(5000); // なんらかの重たい処理を実行 End } } |
StateHasChanged() 補足
.Net Core 5.0の初期の頃、StateHasChanged() を実行しても、変数の変化がブラウザに反映されないことがあった。その際は、await Task.Delay(1) を加えスレッドが切り替わるタイミングを作ることで解消した。
.Net Core 5.0 の最新版でも、OnInitializedAsync() イベントハンドラ、<EditForm>のOnValidSubmitイベントハンドラでは、 await Task.Delay(1) を加えないと StateHasChanged() が上手く機能しない。
処理中Overlayの表示/非表示は、常にこの3行をセットにした方が、悩まなくて済むと思う。
Blazor Server の画面で処理中オーバーレイを表示する
1 2 3 4 5 |
IsLoadingOverlay = true; StateHasChanged(); await Task.Delay(1); // これをしないとスレッドが切り替わらず、オーバーレイが表示されないことがある。StateHasChanged()でもだめ。 |
Blazor Server の仕組みについて
_Host.cshtml
・_Host.cshtml に _framework/blazor.server.js が組み込まれていますが、blazor.server.js が Blazor Server のフロント側の本体です。
・ blazor.server.js がミリ秒単位の頻度でWEBサーバからDOMを取得し、DOMに変化が有ればブラウザ側のHTMLへ反映し、ブラウザ上に表示されている画面をリアルタイムに変化させている。
・ WEBサーバ側のDOMは、StateHasChanged() を実行することで変化する。
・イベントハンドラ実行終了後は StateHasChanged() が自動実行される為、イベントハンドラの実行結果がブラウザに反映される動きに見える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@page "/" @namespace WebApplication1.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @{ Layout = null; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>WebApplication1</title> <base href="~/" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /> <link href="css/site.css" rel="stylesheet" /> <link href="WebApplication1.styles.css" rel="stylesheet" /> </head> <body> <component type="typeof(App)" render-mode="ServerPrerendered" /> <div id="blazor-error-ui"> <environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment> <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="_framework/blazor.server.js"></script> </body> </html> |
Visual Studio プロジェクト
今回使った Visual Studio プロジェクト テンプレートは、Blazor Server 5.0 の認証無し、HTTPS無しです。
コメント