Blazor Server でリッチテキストエディターの HTML を読み取る
Blazor Server のページでは、いずれエディターの HTML を保存するために C# に取り込む必要が出てきます。すぐに思いつく JS.InvokeAsync<string> の呼び出しは、テストでは動作しても、最初の長い文書で失敗します。結果が回線の SignalR 接続を通って送られ、その接続にはメッセージあたり 32 KB の制限があるからです。このページでは、その制限に達したときに何が起きたか、そしてサンプルが代わりにストリームとして HTML を読み取る方法を紹介します。
32 KB の制限
Blazor Server では、ブラウザーからサーバーへのメッセージはすべて回線を通り、JS 相互運用の呼び出し結果もそれに含まれます。回線の SignalR の MaximumReceiveMessageSize は、既定で 32 KB です。InvokeAsync<string> で editor.getHTMLCode() を返して計測した結果は次のとおりです。
- 20,000 文字の HTML:届きました。
- 40,000 文字:ブラウザーのログに「Connection disconnected with error 'Error: Server returned an error on close: Connection closed with an error.'」と記録されました。ページ上のメッセージは変わらず、回線は失われていました。
既定のログレベルでは、サーバー側でこの失敗は何も記録されないため、テストで見落としがちです。
JavaScript からバイト列を返す
JS の関数が Uint8Array を返すと、Blazor はそれを IJSStreamReference として .NET に渡せます。これはメッセージの制限とは別に、チャンクに分けて送信されます。サンプルの相互運用関数は App.razor にあります。
<script>
// Called from Home.razor. The editor options stay in JavaScript, where they can also hold
// functions (custom buttons, event handlers) that cannot be sent from .NET as JSON.
window.rteBlazor = {
create: function (host) {
host.rte = new RichTextEditor(host, {
toolbar: "full",
galleryEndpoint: "/rte/gallery",
maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
});
},
// Returned as bytes: .NET reads them as a stream, so documents over Blazor's 32 KB
// SignalR message limit still arrive.
getHtml: function (host) {
return new TextEncoder().encode(host.rte.getHTMLCode());
},
destroy: function (host) {
if (!host || !host.rte) return;
// editor.destroy() exists only in newer editor builds
if (typeof host.rte.destroy === "function") host.rte.destroy();
host.rte = null;
host.innerHTML = "";
}
};
</script>getHtml は TextEncoder で HTML を UTF-8 にエンコードします。エディター自体は create で作成します。create と destroy については Blazor でリッチテキストエディターに画像ギャラリーを追加するで説明しています。
C# でストリームを読み取る
async Task Save()
{
var bytes = await JS.InvokeAsync<IJSStreamReference>("rteBlazor.getHtml", host);
await using var stream = await bytes.OpenReadStreamAsync(maxAllowedSize: 10_000_000);
using var reader = new StreamReader(stream);
var html = await reader.ReadToEndAsync();
message = $"Saved {html.Length} characters of HTML."; // save html to your database here
}InvokeAsync<IJSStreamReference>は、バイト列を値ではなくストリームとして要求します。OpenReadStreamAsync(maxAllowedSize: 10_000_000)は、サーバーが読み取る最大量を設定します。既定値は 512,000 バイトなので、データベースの列に収まる値に設定してください。StreamReaderは、UTF-8 のバイト列を文字列に戻します。ここから保存し、ほかのユーザーに表示する前にはサニタイズしてください。
このコードでは、100,000 文字の x に画像を 1 つ挿入した文書が、欠けることなく届きました。表示は「Saved 100116 characters of HTML.」です。
制限を引き上げない理由
もう 1 つの方法は、ハブのオプションで MaximumReceiveMessageSize を大きくすることです。たとえば AddInteractiveServerComponents().AddHubOptions(...) を使います。ただし、これはすべての回線のすべてのメッセージについて制限を引き上げます。このページのためにこの方法を使ったりテストしたりはしていません。ストリーミングなら、既定の制限をそのまま維持し、HTML だけを、選んだサイズまで通すことができます。
画像は事情が異なり、そもそもコンポーネントを通りません。エディターが画像を HTTP エンドポイントに送り、保存される HTML には画像の URL だけが含まれる理由については、Blazor でリッチテキストエディターから画像をアップロードするで説明しています。
テストする
このガイドのサンプルプロジェクトを dotnet run で実行し、http://localhost:5304 を開きます。長い文書をエディターに貼り付けるか、いくつかの段落を入力して何度も貼り付けてから、「Save」をクリックします。エディターの下のメッセージに、C# に届いた文字数が表示され、32 KB を大きく超えています。制限を確認するには、getHTMLCode() を文字列として返す JS 関数から InvokeAsync<string> で HTML を読み取るように Save を変更し、同じ文書をもう一度保存します。回線が閉じられます。