Eine Bildergalerie für einen Rich-Text-Editor in ASP.NET Core

Eine Upload-Schaltfläche fügt nur Dateien hinzu. Der Dialog „Insert Gallery“ zeigt außerdem, was bereits auf dem Server liegt, und lässt Benutzer Ordner öffnen, neue anlegen und in den geöffneten Ordner hochladen. In ASP.NET Core braucht der Dialog eine GET-Route, die einen Ordner als JSON auflistet, eine POST-Route für neue Ordner und die Upload-Route. Diese Seite behandelt die Liste und die Auslieferung der Dateien.
Die Schaltfläche und die Option
Die Symbolleiste full enthält bereits die Schaltfläche „Insert Gallery“ (insertgallery). Wenn Sie galleryEndpoint setzen, schaltet der Dialog von einer festen Liste vorgegebener Bilder auf Ihren Server um:
var editor = new RichTextEditor("#editor", {
toolbar: "full",
galleryEndpoint: "/rte/gallery",
maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
});Der Dialog hängt ?folder= an diese URL, um einen Ordner aufzulisten, und sendet für die Schaltfläche „New folder“ action=create-folder per POST an dieselbe URL. Uploads gehen über rte-files.js an /rte/upload.
Die Route
// GET /rte/gallery?folder=products
[HttpGet("gallery")]
public IActionResult Gallery([FromQuery] string? folder) =>
Send(RteFiles.List(HttpContext, folder));Im Minimal-API-Beispiel lautet dieselbe Zeile rte.MapGet("/gallery", (HttpContext http, string? folder) => ...). Beide rufen RteFiles.List auf:
// 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 wandelt den virtuellen Ordner in einen Pfad innerhalb von uploads/ um oder antwortet mit 400 oder 404. Galerieordner in ASP.NET Core erklärt die Methode.
Das JSON, das der Dialog liest
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);
}
parentFolderist im Stammordnernull, wodurch die Schaltfläche „Up“ des Dialogs deaktiviert wird. In einem Ordner ist es der virtuelle Pfad des übergeordneten Ordners,""für einen Ordner der obersten Ebene.- Die Eigenschaften erscheinen in camelCase, weil die JSON-Standardeinstellungen von ASP.NET Core camelCase verwenden, sowohl für
Results.Jsonals auch für dasObjectResultvon MVC. - Aufgeführt werden nur die fünf Bildtypen. Ordner, deren Namen die Ordnerregeln nicht erfüllen, etwa
.git, werden übersprungen. - Namen werden nach ihrer kleingeschriebenen Form mit einem ordinalen Vergleich sortiert.
StringComparer.OrdinalIgnoreCasevergleicht in Großbuchstaben und ordneta_bundaabdaher anders als der Browser. - Jedes URL-Segment wird mit
Uri.EscapeDataStringmaskiert, sodass auch ein Ordner namensMy photoseine funktionierende URL ergibt.
Die Bilder ausliefern
Die URLs zeigen auf /uploads/, ausgeliefert durch einen zweiten Aufruf von UseStaticFiles mit einer eigenen Liste von Content Types:
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",
});Diese Liste ist wichtig. Mit dem standardmäßigen FileExtensionContentTypeProvider würde eine .html- oder .svg-Datei im Ordner als text/html oder image/svg+xml ausgeliefert. ServeUnknownFileTypes auszuschalten, was der Standard ist, blockiert nur Dateiendungen, die der Provider nicht kennt. Mit der Liste des Beispiels gaben eine probe.html und eine probe.svg, die wir in uploads/ gelegt hatten, beide 404 zurück.
Was der Dialog derzeit tut
Der Dialog des ausgelieferten Editors zeigt „Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF“ an. Dieser Server akzeptiert fünf davon. Eine .bmp wird gesendet, der Server antwortet mit 400, und der Dialog zeigt seine eigene Meldung „Upload failed for pic.bmp.“ statt der Meldung des Servers. Eine Datei über maxUploadFileSize wird im Browser mit „big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB.“ abgelehnt. Der Dialog rechnet in binären Megabytes.
Testen
Starten Sie das Beispielprojekt zu dieser Anleitung mit dotnet run, öffnen Sie http://localhost:5302 und klicken Sie auf Insert Gallery. Um das JSON ohne den Dialog zu sehen:
curl "http://localhost:5302/rte/gallery?folder=blog%2F2026"
{"currentFolder":"blog/2026","currentFolderDisplay":"/blog/2026","parentFolder":"blog","folders":[],"images":[]}Dieser Ordner wurde mit der Schaltfläche „New folder“ angelegt. Ein Ordner, der nicht existiert, antwortet mit 404 {"error":"Folder not found."}.
Dieselbe Anleitung für andere Plattformen
JavaScript · Next.js · PHP · Razor Pages · Blazor · MVC 5 · Web Forms