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

Razor Pages のサンプルの「Insert Gallery」ダイアログ。blog、products、team フォルダーと、アップロードされた 4 枚の写真が一覧表示され、1 枚が選択されている

Razor Pages アプリでエディターの画像ギャラリーを作るとき、最初に思いつくのは、OnGetGallery や OnPostUpload のような名前付きハンドラーをページに用意する方法でしょう。しかし、RichTextEditor のギャラリーにはこれは適していません。理由は Razor Pages に特有の 2 つです。このページでは、代わりの方法を紹介します。エディターは通常の Razor Page に置き、ギャラリーの 3 つのルートは同じアプリ内のエンドポイントにします。

ページハンドラーを使わない理由

  • URL が合いません。名前付きハンドラーには /?handler=Upload としてアクセスします。エディターのアップロードスクリプトは <base>/upload に POST し、ギャラリーは一覧のたびに galleryEndpoint に ?folder= を付けるため、ハンドラーの URL を使うには両方を変更する必要があります。
  • すべての POST ハンドラーが偽造防止トークンをチェックします。これは Razor Pages の既定の動作であり、望ましいものです。エディターのアップロードと「New folder」のリクエストはエディター自身が作る XHR で、現在のリリースではそれらにトークンを追加できません。ハンドラーを使うには、ページモデルに [IgnoreAntiforgeryToken] を付ける必要があり、そうするとページ自体の「Save」フォームでもチェックが無効になってしまいます。

エンドポイントにすれば、両者を分けておけます。ページとそのフォームは完全な保護を維持し、JSON のルートはそれぞれ明示的に選択できます。

Program.cs

C# · Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();

app.UseStaticFiles();
app.UseRteStaticFiles();   // /uploads, /richtexteditor, /rte-files.js

app.MapRazorPages();       // "/" -> Pages/Index.cshtml; its own form POST keeps antiforgery validation
app.MapRteFiles();         // GET+POST /rte/gallery, POST /rte/upload

app.Run();

MapRteFiles() は、ほかの ASP.NET Core のサンプルが使っているものと同じ最小 API のエンドポイント群です。GET と POST /rte/gallery、そして POST /rte/upload です。UseRteStaticFiles() は、/uploads(5 種類の画像だけ)、/richtexteditor のエディター、/rte-files.js を配信します。

ページ

Razor · Pages/Index.cshtml
<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>

エディターは、ページの下のほうで galleryEndpoint: "/rte/gallery" を指定して作成します。その部分はフォームと合わせて Razor Pages でリッチテキストエディターから画像をアップロードするで紹介しています。

一覧のルート

C# · RteEndpoints.cs
rte.MapGet("/gallery", (HttpContext http, string? folder) =>
    Json(RteFiles.List(http, folder)));

レスポンスはフォルダーの JSON です。currentFolder、parentFolder(ルートでは null)、サブフォルダー、そして URL、名前、サイズを含む画像があります。JSON もコードも ASP.NET Core のギャラリーガイドと同じで、各フィールドの説明もそちらにあります。

POST ルートと偽造防止

C# · RteEndpoints.cs
// Binding [FromForm] or IFormFile makes .NET 8 demand an antiforgery token on these endpoints.
// The editor's XHRs cannot send one, so it is switched off here and RteFiles checks the
// request's origin instead (see RequireSameOrigin). guide-notes.md has the details.
rte.MapPost("/gallery", (HttpContext http, [FromForm] string? action, [FromForm] string? folder, [FromForm] string? name) =>
    Json(RteFiles.CreateFolder(http, action, folder, name)))
    .DisableAntiforgery();

ここに Razor Pages の 2 つ目の注意点があります。AddRazorPages() は偽造防止のサービスを登録しますが、.NET 8 では、最小 API のエンドポイントが使う偽造防止のミドルウェアは追加しません。IFormFile や [FromForm] をバインドするエンドポイントには偽造防止のメタデータが付くため、.DisableAntiforgery() がないと、そのエンドポイントへの POST はすべて 500 で失敗しました。メッセージは「contains anti-forgery metadata, but a middleware was not found that supports anti-forgery」です。代わりに app.UseAntiforgery() を追加すると、エディターがトークンを送信しないため、これが 400 になります。この 2 つのルートで偽造防止を無効にし、その代わりにリクエストのオリジンをチェックすることで、ルートが機能するようになります。このチェックについては Razor Pages での安全な画像アップロードで説明しています。

テストする

このガイドのサンプルプロジェクトを dotnet run(.NET 8 SDK)で実行し、http://localhost:5303 を開いて「Insert Gallery」をクリックします。ダイアログには uploads/ 以下のフォルダーと画像が一覧表示されます。シェルから確認すると、アップロード用のルートはトークンなしで応答しますが、ページ自体の POST は応答しません。

Shell
curl -F "file=@photo.jpg" http://localhost:5303/rte/upload
{"url":"/uploads/photo-4cdaeb3e.jpg"}

curl -s -o /dev/null -w "%{http_code}\n" -d "Html=x" http://localhost:5303/
400

この 400 は、偽造防止トークンのないフォームの POST を Razor Pages が拒否したもので、ページとしては望ましい動作です。

ほかのプラットフォーム向けの同じガイド

JavaScript · Next.js · PHP · ASP.NET Core · Blazor · MVC 5 · Web Forms

次に読む