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

RichTextEditor 是一个 JavaScript 编辑器,因此在 Blazor Web App 中要通过 JS 互操作来创建和移除它,而图片库则与组件旁边的普通 HTTP 端点通信。本页介绍其中与 Blazor 相关的部分:编辑器的脚本和选项放在哪里、组件何时可以创建编辑器、如何再次移除它,以及图片库的路由在 Program.cs 中如何安排。本页使用 .NET 8 中的 Interactive Server 渲染模式。
App.razor 中的脚本和选项
编辑器的文件、共享的 rte-files.js 和三个小型互操作函数放在 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)用 JavaScript 编写,而不是从 C# 传入。选项中可以包含函数,例如自定义按钮和事件处理函数,而函数无法以 JSON 形式从 .NET 传过来。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 确保编辑器只创建一次,而不是每次重新渲染都创建。标记中的 @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() 以 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 中也能得到列表响应:
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