Blazor でリッチテキストエディターに画像ギャラリーを追加する

RichTextEditor は JavaScript のエディターなので、Blazor Web App では JS 相互運用で作成・削除し、ギャラリーはコンポーネントと並ぶ通常の HTTP エンドポイントとやり取りします。このページでは Blazor に関わる部分を紹介します。エディターのスクリプトとオプションの置き場所、コンポーネントがエディターを作成してよいタイミング、エディターを削除する方法、そしてギャラリーのルートを Program.cs にどう置くかです。.NET 8 の Interactive Server レンダリングを使います。
App.razor のスクリプトとオプション
エディターのファイル、共通の rte-files.js、そして 3 つの小さな相互運用関数は、App.razor の <head> に置きます。このファイルは一度だけレンダリングされ、対話型コンポーネントによってレンダリングされるものではありません。
<link rel="stylesheet" href="richtexteditor/rte_theme_default.css" />
<script src="richtexteditor/rte.js"></script>
<script src="richtexteditor/plugins/all_plugins.js"></script>
<script src="rte-files.js"></script>
<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>galleryEndpoint を含むエディターのオプションは、C# から送るのではなく JavaScript で記述します。オプションには、カスタムボタンやイベントハンドラーなどの関数を含めることができ、関数は .NET から JSON として渡せないからです。getHtml については Blazor Server でエディターの HTML を読み取るで説明しています。
エディターの作成
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
await JS.InvokeVoidAsync("rteBlazor.create", host);
created = true;
}このページには @rendermode InteractiveServer が指定されており、Blazor はこれをプリレンダリングします。つまり、最初のレンダリングはブラウザーが接続されていない状態でサーバー上で行われます。このときは JS 相互運用を使えません。OnAfterRenderAsync は回線が接続された後にだけ実行され、firstRender によって、再レンダリングのたびではなく 1 回だけエディターが作成されるようにしています。マークアップ内の @ref="host" 要素は空の <div> です。Blazor はその中に何も書き込まないので、DOM をめぐってエディターと競合することはありません。
エディターの削除
public async ValueTask DisposeAsync()
{
if (!created) return;
try { await JS.InvokeVoidAsync("rteBlazor.destroy", host); }
catch (JSDisconnectedException) { } // the browser tab is already gone
}createdフラグ。これがないと、コンポーネントのプリレンダリング版を破棄するときにInvalidOperationException: JavaScript interop calls cannot be issued at this timeがスローされました。JSDisconnectedExceptionは、ユーザーがタブを閉じたときに発生します。ブラウザーがなくなった後でコンポーネントが破棄されるためです。destroy()がない場合があります。テストしたリリース版のエディターのビルドにはeditor.destroy()がないため、rteBlazor.destroyはそれが存在する場合にだけ呼び出し、その後ホスト要素を空にします。
ギャラリーのルート
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
var app = builder.Build();
app.UseStaticFiles();
app.UseRteStaticFiles(); // /uploads, /richtexteditor, /rte-files.js
app.UseAntiforgery(); // Blazor's own forms; the /rte endpoints opt out (RteEndpoints.cs)
app.MapRteFiles(); // GET+POST /rte/gallery, POST /rte/upload
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
app.Run();MapRteFiles() は、GET と POST /rte/gallery、そして POST /rte/upload を最小 API のエンドポイントとして追加します。ギャラリーの一覧は、ASP.NET Core のギャラリーガイドと同じ JSON です。ルートは MapRazorComponents より前にマップし、POST ルートでは .DisableAntiforgery() を呼び出します。そうしないと、テンプレートの app.UseAntiforgery() がエディターのリクエストを拒否してしまうからです。ファイルを回線経由で送らない理由については、Blazor でリッチテキストエディターから画像をアップロードするで説明しています。
現在のダイアログの動作
リリース版のダイアログには「Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF」と表示されます。サーバーが受け付けるのはそのうち 5 つなので、.bmp は送信された後 400 で拒否され、ダイアログはサーバーのメッセージではなく、独自の「Upload failed for pic.bmp.」を表示します。
テストする
このガイドのサンプルプロジェクトを dotnet run(.NET 8 SDK)で実行し、http://localhost:5304 を開きます。ツールバーが表示されるのを待ってから「Insert Gallery」をクリックします。ページ上部の画像のように、アプリの uploads/ フォルダー以下のフォルダーと画像が表示されます。一覧はシェルからも取得できます。
curl "http://localhost:5304/rte/gallery?folder=team"
{"currentFolder":"team","currentFolderDisplay":"/team","parentFolder":"","folders":[],"images":[]}ほかのプラットフォーム向けの同じガイド
JavaScript · Next.js · PHP · ASP.NET Core · Razor Pages · MVC 5 · Web Forms