在 ASP.NET Core 中为富文本编辑器添加图片库

上传按钮只能添加文件。而 Insert Gallery 对话框还能显示服务器上已有的内容,让用户打开文件夹、新建文件夹,并上传到当前打开的文件夹。在 ASP.NET Core 中,这个对话框需要一个以 JSON 列出文件夹内容的 GET 路由、一个用于新建文件夹的 POST 路由,以及上传路由。本页介绍列表部分以及如何提供文件。
按钮和选项
full 工具栏已经包含 Insert Gallery 按钮(insertgallery)。设置 galleryEndpoint 后,对话框不再显示一组固定的预设图片,而是改用您的服务器:
var editor = new RichTextEditor("#editor", {
toolbar: "full",
galleryEndpoint: "/rte/gallery",
maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
});对话框在该 URL 后附加 ?folder= 来列出文件夹,并在点击 New folder 按钮时向同一 URL POST action=create-folder。上传则通过 rte-files.js 发送到 /rte/upload。
路由
// GET /rte/gallery?folder=products
[HttpGet("gallery")]
public IActionResult Gallery([FromQuery] string? folder) =>
Send(RteFiles.List(HttpContext, folder));在 Minimal API 示例中,对应的一行是 rte.MapGet("/gallery", (HttpContext http, string? folder) => ...)。两者都调用 RteFiles.List:
// GET /rte/gallery?folder=products
public static RteResult List(HttpContext http, string? folder) => Run(() =>
{
var root = RootFor(http);
folder ??= "";
return Listing(root, folder, Resolve(root, folder));
});Resolve 把虚拟文件夹转换为 uploads/ 内的路径,或者返回 400 或 404。ASP.NET Core 中的图片库文件夹对此做了说明。
对话框读取的 JSON
static RteListing Listing(RteRoot root, string folder, string disk)
{
var dir = new DirectoryInfo(disk);
var folders = dir.EnumerateDirectories()
.Where(d => IsValidName(d.Name))
.OrderBy(d => d.Name.ToLowerInvariant(), StringComparer.Ordinal)
.Select(d => new RteFolder(folder.Length == 0 ? d.Name : folder + "/" + d.Name, d.Name))
.ToList();
var images = dir.EnumerateFiles()
.Where(f => !f.Name.StartsWith('.') && Extensions.Contains(f.Extension.ToLowerInvariant()))
.OrderBy(f => f.Name.ToLowerInvariant(), StringComparer.Ordinal)
.Select(f => new RteImage(UrlFor(root, folder, f.Name), f.Name, folder, f.Length))
.ToList();
string? parent = folder.Length == 0 ? null : folder.Contains('/') ? folder[..folder.LastIndexOf('/')] : "";
return new RteListing(folder, "/" + folder, parent, folders, images);
}
- 在根目录下,
parentFolder为null,这会禁用对话框的 Up 按钮。在子文件夹中,它是父文件夹的虚拟路径,对于顶层文件夹则为""。 - 属性名采用 camelCase,因为 ASP.NET Core 的 JSON 默认设置就是 camelCase,
Results.Json和 MVC 的ObjectResult都是如此。 - 只列出五种图片类型。名称不符合文件夹规则的文件夹(例如
.git)会被跳过。 - 名称按小写形式以序号比较进行排序。
StringComparer.OrdinalIgnoreCase按大写形式比较,因此它对a_b和aab的排序与浏览器不同。 - URL 的每一段都用
Uri.EscapeDataString转义,因此名为My photos的文件夹也能得到可用的 URL。
提供图片
这些 URL 指向 /uploads/,由第二个 UseStaticFiles 调用提供,它有自己的内容类型列表:
var uploads = RteFiles.UploadsFolder(app.Environment);
Directory.CreateDirectory(uploads);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(uploads),
RequestPath = "/uploads",
ContentTypeProvider = new FileExtensionContentTypeProvider(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[".jpg"] = "image/jpeg", [".jpeg"] = "image/jpeg", [".png"] = "image/png",
[".gif"] = "image/gif", [".webp"] = "image/webp",
}),
OnPrepareResponse = ctx => ctx.Context.Response.Headers.XContentTypeOptions = "nosniff",
});这个列表很重要。如果使用默认的 FileExtensionContentTypeProvider,文件夹中的 .html 或 .svg 文件会以 text/html 或 image/svg+xml 提供。关闭 ServeUnknownFileTypes(这是默认设置)只能阻止提供程序不认识的扩展名。使用示例中的列表后,我们放进 uploads/ 的 probe.html 和 probe.svg 都返回了 404。
对话框目前的行为
已发布版本编辑器的对话框显示 "Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF"(接受的格式)。这个服务器只接受其中五种。.bmp 文件会被发送出去,服务器返回 400,对话框显示的是它自己的 "Upload failed for pic.bmp.",而不是服务器返回的消息。超过 maxUploadFileSize 的文件会在浏览器中被拒绝,提示 "big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB."。对话框按二进制兆字节(MiB)计算。
测试
用 dotnet run 运行本指南的示例项目,打开 http://localhost:5302 并点击 Insert Gallery。不通过对话框直接查看 JSON:
curl "http://localhost:5302/rte/gallery?folder=blog%2F2026"
{"currentFolder":"blog/2026","currentFolderDisplay":"/blog/2026","parentFolder":"blog","folders":[],"images":[]}这个文件夹是用 New folder 按钮创建的。不存在的文件夹会返回 404 {"error":"Folder not found."}。
其他平台的同一指南
JavaScript · Next.js · PHP · Razor Pages · Blazor · MVC 5 · Web Forms