在 Razor Pages 中从富文本编辑器上传图片

在 Razor Pages 应用中,围绕编辑器有两种不同的 POST 请求。每张图片在插入的那一刻就会上传到一个返回其 URL 的端点。之后,页面自己的表单再把包含这些 URL 的最终 HTML POST 到 OnPost。本页介绍这两种请求,以及为什么要把它们分开:表单保留自己的防伪造令牌,而 HTML 也能保持足够小,不超出 Razor Pages 的表单限制。
表单
<form method="post" id="post">
<input type="hidden" asp-for="Html" />
<div id="editor"></div>
<p><button type="submit">Save</button> @Model.Message</p>
</form>
<script>
var editor = new RichTextEditor("#editor", {
toolbar: "full",
galleryEndpoint: "/rte/gallery",
maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
});
var html = document.getElementById("Html");
editor.setHTMLCode(html.value);
document.getElementById("post").addEventListener("submit", function () {
html.value = editor.getHTMLCode();
});
</script><input type="hidden" asp-for="Html" />承载 HTML。<form method="post">标记帮助程序会自动添加隐藏的__RequestVerificationToken字段。- 编辑器以隐藏字段的值作为初始内容,因此 POST 之后,页面会再次显示已保存的内容。
- 提交处理函数在表单发送之前,把
editor.getHTMLCode()复制到该字段中。
页面模型
// Razor Pages validates the antiforgery token on this POST by default; the <form> tag helper
// in Index.cshtml adds the hidden __RequestVerificationToken field. Save Html to your database here.
public void OnPost() => Message = $"Saved {Html?.Length ?? 0} characters of HTML.";Html 上的 [BindProperty] 会在 POST 时绑定该字段。Razor Pages 在 OnPost 运行之前验证防伪造令牌;不带令牌的 POST 会得到 400。在存储 HTML 或把它展示给其他用户之前,请在服务器上对它进行净化:编辑器发送的内容仍然是来自浏览器的输入。
为什么先上传图片
没有上传处理程序时,编辑器会把粘贴的图片以 base64 data: URL 的形式嵌入 HTML,这是一段很长的文本。Razor Pages 的表单值受 FormOptions.ValueLengthLimit 限制,默认为 4,194,304 个字符。在令牌有效的情况下,100,000 个字符的表单 POST 能够保存,而 4,300,000 个字符的 POST 在 OnPost 运行之前就得到了 400。有了上传路由,HTML 中每张图片只占一个短 URL。在空编辑器中插入一张已上传的图片并点击 Save 后,页面显示 "Saved 108 characters of HTML."。
上传路由
页面上加载的 rte-files.js 会把每张图片发送到 /rte/upload:包括拖放、粘贴、Insert Image 对话框和图片库的 Upload 按钮。这个路由是在 Program.cs 中用 app.MapRteFiles() 映射的 Minimal API 端点:
rte.MapPost("/upload", async (HttpContext http, IFormFile? file, [FromForm] string? folder) =>
Json(await RteFiles.Save(http, file, folder)))
.DisableAntiforgery()
// Kestrel accepts 30,000,000-byte bodies by default; this route needs ~4 MB.
.WithMetadata(new RequestSizeLimitAttribute(RteFiles.MaxRequestBytes))
.WithFormOptions(multipartBodyLengthLimit: RteFiles.MaxRequestBytes);它不能是页面处理程序,因为处理程序的地址形如 /?handler=Upload,而且每个 POST 处理程序都要求防伪造令牌,编辑器的 XHR 无法发送这个令牌。在 Razor Pages 中为富文本编辑器添加图片库解释了这一选择。这两个限制让请求保持在 4,065,536 字节以内;否则 Kestrel 会接受最多 30,000,000 字节。随后 RteFiles.Save 检查来源、文件夹、大小、扩展名和文件开头的字节,并以新名称保存文件。这些检查与 ASP.NET Core 上传指南中的相同。

测试
用 dotnet run 运行本指南的示例项目,然后打开 http://localhost:5303。从图片库插入一张图片(也可以先用 Upload 按钮上传一张),然后点击 Save:消息会显示 HTML 的长度,页面重新加载后图片仍然在。在 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上传不需要令牌也能成功。而单纯的表单 POST 会被拒绝,因为它没有令牌。
其他平台的同一指南
JavaScript · Next.js · PHP · ASP.NET Core · Blazor · MVC 5 · Web Forms