本エントリはBlazor Advent Calender 2019の4日目の記事です
はじめに
.NET Core 3.0がリリースされてから3か月近く経過しました。
※追記(12/04):本日.NET Core 3.1 LTSがリリースされました!
皆さんはお仕事でBlazor書いてますでしょうか?勿論私は書けていません。
産まれて間もないですし、中々難しいところではあります。
※海外ではBlazorチョットデキル人への求人がちょこちょこ出てきているようですが...。
#Blazor starting to appear in job ads... https://t.co/dUEP3bbiN8 - this is for an #F1 team - possibly Racing Point?
— Howard Richards (@conficient) 2019年11月15日
Any #Blazor devs in the Tampa/Clearwater/St. Pete FL area looking for a good FTE role? Our company is migrating legacy apps to Blazor and have open reqs. This is real app dev, not weather forecast demo stuff, and it's a lot of fun. DM if you are interested and qualified.
— Sean Moran #Blazor (@spmoran) 2019年11月30日
どうにかBlazorを合法的に使用できないものか?
弊社の場合フルスクラッチでBlazorのSPAを作成するのは政治的な理由で難しく、既存のASP.NET Core MVCにComponent単位で組込む程度が現実的でしょう。
※BlazorのComponent(Razor Componenet)は普通のASP.NET Core MVCでも表示可能なので、SignalRとの通信さえできればMVC上でServer side Blazorできるはず。
というわけで「既存のASP.NET Core MVCをComponent単位でBlazorに置き換えてく作戦」で行きたいと思います。
留意点
とはいえ、手放しにServer side Blazorを組込むことは出来ません。
何個か留意点があります。
SignalRと常時通信が発生するためクライアント側に安定した接続環境が必要となる。
SignalRとの通信タイムアウトやBlazorのエラーハンドリングが必要 ※最新のBlaozrなら
<div id="blazor-error-ui"></div>
とかが用意されてるので苦ではないかも。サーバーのスペックとかスケーリングの見積
ちなみに、3番についてはMSの人が多少検証したっぽいです。
上記Blogから引用します。
In our tests, a single Standard_D1_v2 instance on Azure (1 vCPU, 3.5 GB memory) could handle over 5,000 concurrent users without any degradation in latency. A Standard_D3_V2 instance (4 vCPU, 14GB memory) handled well over 20,000 concurrent clients. The main bottleneck for handling further load was available memory.
メモリ量が大事っぽいですね。
では、まずはRazor Componentの表示からやってみましょう。
ASP.NET Core MVC Projectの作成
ベースとなるASP.NET Core MVCプロジェクトを作成します。
※筆者の開発環境は.NET Core 3.1 Preview3です。
※追記(12/04):本日リリースされた.NET Core 3.1 LTSでOKです!
dotnet new mvc -o BlazorOnMVC cd BlazorOnMVC
Views/Home/BlazorComponent.razorの作成
Blazorのコンポーネントを作成します。
<h1>Hello @Name!</h1> <p>This is Blazor Components.</p> @code { [Parameter] public string Name {get; set;} }
Views/Home/Index.cshtmlの編集
作成したRazor Componentを<component />
で表示させます。
この辺りは.NET Core 3.1 Preview2で変更が入った書き方ですね。
param-Name
で文字列を引数として渡していますが
param-Name="Blazor"
だとerror CS0103: 現在のコンテキストに Blazor という名前は存在しません。
と怒られます。
しゃーないのでStringを@("文字列")
で括って与えています。
+ @using BlazorOnMVC.Views.Home @{ ViewData["Title"] = "Home Page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> </div> + <component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" param-Name="@("Blazor")"/>
ちなみに過去のバージョンでのRazor Componentの表示方法は以下の通りです。
// .NET Core 3.0 Preview8 までの書き方 @(await Html.RenderComponentAsync<BlazorComponent>()) // .NET Core 3.0 Preview9 から.NET Core 3.1 Preview1 までの書き方 @(await Html.RenderComponentAsync<BlazorComponent>(RenderMode.ServerPrerendered)) // .NET Core 3.1 Preview2 からの書き方 <component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" />
とりあえず以上です。
Razor Componentが描画されるかどうか、実行してみます。
実行結果
ASP.NET Core MVCでRazor Componentの表示に成功しました。
続いてSignalRと通信するための部分を追加します。
Startup.csの編集
Startup.csを編集します。
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); + services.AddServerSideBlazor(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 途中省略 app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); + endpoints.MapBlazorHub(); }); }
Views/Home/Index.cshtmlの編集
blazor.server.js
を埋め込みます。
_Layout.cshtml
に埋め込んでも良いですが、Razor Componentを使用していないページでもSignalRとの通信が発生してしまいます。
よってRazor Componentを使用するページであるIndex.cshtml
にblazor.server.js
を埋め込みます。
@using BlazorOnMVC.Views.Home + <script src="_framework/blazor.server.js" autostart="false"></script> @{ ViewData["Title"] = "Home Page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> </div> <component type="typeof(BlazorComponent)" render-mode="ServerPrerendered" param-Name="@("Blazor")" /> + <script> + window.onload = function() { + Blazor.start({ + configureSignalR: function (builder) { + builder.configureLogging(2); // 2はLogLevel.Information + }, + logLevel: 2, + reconnectionOptions: { + maxRetries: 3, + retryIntervalMilliseconds: 2000, + } + }); + } + </script>
_Layout.cshtml
ならautostart="true"
で正常に動作しますが、Index.cshtml
だとうまく動作しませんでした。
仕方ないのでautostart="false"
を指定しwindow.onload
のタイミングでSignalRとの通信を開始しています。ライフサイクルの関係でしょうか...。
なおautostart
は省略可能です。省略した場合autostart="true"
で動作します。
ソース的にはこことかここら辺っぽいですね
LogLevelの指定はこちらをご参照ください。
BlazorComponent.razorの編集
Razor Componentに手を加えます。
ボタンを押下した回数を画面に表示する処理を追加しました。
注意点としてはMicrosoft.AspNetCore.Components.Web
を参照に追加するのを絶対に忘れないでください。
上記参照がない状態だと、ボタンを押下しても@onclick
にbindしたメソッドが実行されません。しかもエラーも何も表示されません...😖
+ @using Microsoft.AspNetCore.Components.Web <h1>Hello @Name!</h1> <p>This is Blazor Components.</p> + <p>Counter: @Count</p> + <button @onclick="IncrementCount">Count++</button> @code { [Parameter] public string Name {get; set;} + int Count = 0; + void IncrementCount() + { + Count++; + } }
実行結果
dotnet run
で起動します。
WebSocket connected
って書いてありますね。
念のためNetworkタブを見たところ、ちゃんとSignalRと通信してるっぽいです。
数秒おきにSignalRとHeartBeatしてるログが表示されています。
Count++
ボタンを押下すると画面上のCountの数値がインクリメントされていきます。
Server side Blazorが動きました!
おわり
というわけで、無事ASP.NET Core MVCにServer side Blazor(Razor Component)を乗せることに成功しました。「既存のASP.NET Core MVCをComponent単位でBlazorに置き換えてく作戦」は実現可能っぽいですね。
今回はRazor Componentを組込むだけですが、MapFallbackToPage
を使えばMapControllerRoute
によるRoutingを保ちつつ一部のページをRazor PageでSPA化もできるのかな?いずれチャレンジしてみたいと思います。。
以上です。