在 Razor Pages 中为富文本编辑器添加图片库

Razor Pages 示例的 Insert Gallery 对话框,列出 blog、products 和 team 文件夹以及四张已上传的照片,其中一张处于选中状态

在 Razor Pages 应用中,为编辑器的图片库首先想到的做法通常是在页面上写一组命名处理程序,例如 OnGetGallery 和 OnPostUpload。但对于 RichTextEditor 的图片库来说,它们并不合适,原因有两个,而且都是 Razor Pages 特有的。本页介绍替代做法:把编辑器放在普通的 Razor Page 上,而把三个图片库路由作为同一应用中的端点。

为什么不用页面处理程序

  • URL 对不上。命名处理程序的访问地址形如 /?handler=Upload。编辑器的上传脚本 POST 到 <base>/upload,而图片库每次请求列表时都会在 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 示例也在使用的同一组 Minimal API 端点:GET 和 POST /rte/gallery,以及 POST /rte/upload。UseRteStaticFiles() 提供 /uploads(仅限五种图片类型)、位于 /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、名称和大小的图片。它与 ASP.NET Core 图片库指南中的 JSON 和代码完全相同,那里解释了每个字段。

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 的第二个细节。AddRazorPages() 会注册防伪造服务,但在 .NET 8 中,它不会添加 Minimal API 端点使用的防伪造中间件。绑定 IFormFile 或 [FromForm] 的端点仍然带有防伪造元数据,因此没有 .DisableAntiforgery() 时,每个发往它的 POST 都以 500 失败:"contains anti-forgery metadata, but a middleware was not found that supports anti-forgery"。如果改为添加 app.UseAntiforgery(),错误会变成 400,因为编辑器不发送令牌。在这两个路由上禁用验证,并以检查请求来源作为替代,才能让它们正常工作。Razor Pages 中的安全图片上传介绍了这项检查。

测试

用 dotnet run(需要 .NET 8 SDK)运行本指南的示例项目,打开 http://localhost:5303 并点击 Insert Gallery。对话框会列出 uploads/ 下的文件夹和图片。在 shell 中,上传路由不需要令牌就能响应,而页面自己的 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 是 Razor Pages 拒绝了不带防伪造令牌的表单 POST,这正是页面所需要的行为。

其他平台的同一指南

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

延伸阅读