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

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

RichTextEditor 是一个 JavaScript 编辑器,因此在 Blazor Web App 中要通过 JS 互操作来创建和移除它,而图片库则与组件旁边的普通 HTTP 端点通信。本页介绍其中与 Blazor 相关的部分:编辑器的脚本和选项放在哪里、组件何时可以创建编辑器、如何再次移除它,以及图片库的路由在 Program.cs 中如何安排。本页使用 .NET 8 中的 Interactive Server 渲染模式。

App.razor 中的脚本和选项

编辑器的文件、共享的 rte-files.js 和三个小型互操作函数放在 App.razor 的 <head> 中,它只渲染一次,且不由交互式组件渲染:

Razor · Components/App.razor
<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)用 JavaScript 编写,而不是从 C# 传入。选项中可以包含函数,例如自定义按钮和事件处理函数,而函数无法以 JSON 形式从 .NET 传过来。getHtml 的说明见在 Blazor Server 中读取编辑器的 HTML。

创建编辑器

Razor · Components/Pages/Home.razor
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender) return;
    await JS.InvokeVoidAsync("rteBlazor.create", host);
    created = true;
}

页面带有 @rendermode InteractiveServer,Blazor 会对它进行预渲染:第一次渲染在服务器上进行,此时没有浏览器连接,无法进行 JS 互操作。OnAfterRenderAsync 只在线路连接之后运行,而 firstRender 确保编辑器只创建一次,而不是每次重新渲染都创建。标记中的 @ref="host" 元素是一个空的 <div>;Blazor 从不向其中写入内容,因此不会与编辑器争夺 DOM。

移除编辑器

Razor · Components/Pages/Home.razor
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 只在它存在时才调用,然后清空宿主元素。

图片库路由

C# · Program.cs
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() 以 Minimal API 端点的形式添加 GET 和 POST /rte/gallery,以及 POST /rte/upload。图片库的列表与 ASP.NET Core 图片库指南中的 JSON 相同。这些路由在 MapRazorComponents 之前映射,POST 路由调用了 .DisableAntiforgery(),否则模板中的 app.UseAntiforgery() 会拒绝编辑器的请求。在 Blazor 中从富文本编辑器上传图片解释了文件为什么不经过线路传输。

对话框目前的行为

已发布版本的对话框列出 "Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF"(接受的格式)。服务器只接受其中五种,因此 .bmp 文件会被发送,然后以 400 被拒绝,对话框显示的是它自己的 "Upload failed for pic.bmp.",而不是服务器返回的消息。

测试

用 dotnet run(需要 .NET 8 SDK)运行本指南的示例项目,打开 http://localhost:5304,等工具栏出现后点击 Insert Gallery。您会看到应用 uploads/ 文件夹下的文件夹和图片,就像顶部的图片那样。在 shell 中也能得到列表响应:

Shell
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

延伸阅读