この記事は、私の試行錯誤の記録であり、だいぶ良いところまで行ってるものの、道半ばです。
より良いアイデア・ドキュメント等ご存知の方がいらっしゃいましたら、教えてほしいです。
はじめに
Blazor Serverで作ったページを開くと、ブラウザとサーバが自動でSignalRで接続される。
ページ遷移等におけるUIの変更がサーバ側で処理され、DOMの差分がこのSignalRの回線を通してブラウザに届くことで、すごいいい感じにUIが更新される。
しかし!タブブラウザとかでタブが休眠すると、SignalRが切れ、再接続がイマイチだったりして、いいところばっかじゃない。
動的な画面更新とか、フォームとかはSignalR繋がっても良いけど、そうでないページはSignalR繋がないことはできるのか?という話。
↑上記の記事を参考にしました。この記事では、SignalRが完全に使えない状況を想定していて、すべてのページを静的にしている。
今回想定しているのは、SignalR使えなくはないけど、必要に応じてのみ使いたい状況。
とりあえずの解決
まず、Blazor Serverのプロジェクトを新規作成しましょう。一応.Net6のテンプレートで説明を進めます。まぁ、.Net7も大体いっしょ。
修正場所は、以下の2点。
まず、SignalRの接続をつかさどっている大元、Pages/_Layout.cshtmlの下の方にあるJavaScriptの部分。これを、必要な時のみ読み込ませる(レンダリングする)ようにすればよい。
<script src="_framework/blazor.server.js"></script>
次に、Pages/_Host.cshtmlの、コンポーネントのレンダリング開始場所。このrender-modeを調整することで、サーバのふるまいを調整する。
<component type="typeof(App)" render-mode="ServerPrerendered" />
①_Host.cshtmlの修正
リクエストの時に呼び出される順番は、_Hostが先なので、まずこっちから修正する。
@{
Layout = "_Layout";
var mode = RenderMode.Static;
var path = Request.Path.Value!;
var page = new[] { "/create", "/edit", "/delete" };
if (page.Any(t => path.Contains(t, StringComparison.OrdinalIgnoreCase))) mode = RenderMode.Server;
ViewBag.RenderMode = mode;
}
<component type="typeof(App)" render-mode="@mode" />
リクエストされたURLによって、render-modeを変えるようにした。
render-modeについては以下の通り。
- RenderMode.ServerPrerendered
- 先にサーバでレンダリングし、ブラウザに完成したHtmlを送る。
ブラウザはそれを表示した後にSignalR接続を開始し、接続後もう一度レンダリングする。以後、UIの変更はSignalR接続により処理される。つまり初回接続時は、2回レンダリングされる。
(JavaScriptを実行できない)検索サイトのクローラとかでも、ページのコンテンツを取得できる利点がある。
- 先にサーバでレンダリングし、ブラウザに完成したHtmlを送る。
- RenderMode.Static
- 先にサーバでレンダリングし、ブラウザに完成したHtmlを送る。
ブラウザはそれを表示した後SignalR接続を開始するが、接続を介してのレンダリングはしない。SignalR接続されるので、接続を介してのレンダリングはしないものの、接続が切れたりすると再接続が求められる。
- 先にサーバでレンダリングし、ブラウザに完成したHtmlを送る。
- RenderMode.Server
- 先にレンダリングしない。ブラウザには、テンプレート部分だけ送られる。
ブラウザはそれを表示した後SignalR接続を開始し、コンテンツ部分をレンダリングする。レンダリングは1回。
JavaScriptが動かないと、画面にテンプレート部分しか表示されない。
- 先にレンダリングしない。ブラウザには、テンプレート部分だけ送られる。
今回の例では、普通のページをRenderMode.Staticにし、フォームのページをRenderMode.Serverにしている。
フォームのページは、SignalR接続しなきゃ機能を果たさないし、検索エンジン対策する必要もないので、ServerPrerenderedではなくServerにしている。レンダリング1回になるので、その方が都合がよい。
ここでrender-modeをどれにしても、SignalRは接続される。Staticにしたときは、接続しないようにしたい。
そこで、modeをViewBagに突っ込んで、次へ…
②_Layout.cshtmlの修正
@if (ViewBag.RenderMode != RenderMode.Static)
{
<script src="_framework/blazor.server.js"></script>
}
このJavaScriptさえ読み込ませなければ、SignalRは接続されない。そこで、ViewBagの中身を確認して、Staticの時にそもそもこの行をレンダリングしないようにする。
これでSignalRが接続されなくなって、うれしい!
現状の問題
上述した方法だと、問題がある。
- RenderModeがStaticのページのみを遷移しているとき…
- 遷移時に毎回サーバにリクエストされて完璧。
- 一旦Staticではないページ(フォームとか)に入っちゃうと…
- そこでSignalR接続され、以後Staticであってほしいページに戻っても、回線を使ってコンポーネント以下でUIが更新されるようになってしまう。
NavigationManagerで強制移動してリロードさせちゃえば、またStaticに戻るんだけど、すべてのリンクでやるのはめんどくさい…
Staticなページがメインなサイト(管理者しかフォームのページに行かない…)とかなら、とりあえずこれで良いかな…