Ajouter une galerie d’images à un éditeur de texte enrichi avec ASP.NET Core

La boîte de dialogue Insert Gallery qui affiche les dossiers blog, products et team et quatre photos du dossier uploads de l’exemple ASP.NET Core, l’une d’elles sélectionnée

Un bouton d’envoi ne fait qu’ajouter des fichiers. La boîte de dialogue Insert Gallery montre aussi ce qui se trouve déjà sur le serveur, et permet d’ouvrir des dossiers, d’en créer de nouveaux et de téléverser des fichiers dans le dossier ouvert. Avec ASP.NET Core, la boîte de dialogue a besoin d’une route GET qui liste un dossier en JSON, d’une route POST pour les nouveaux dossiers et de la route d’envoi. Cette page traite de la liste et de la façon dont les fichiers sont servis.

Le bouton et l’option

La barre d’outils full contient déjà le bouton Insert Gallery (insertgallery). Définir galleryEndpoint fait passer la boîte de dialogue d’une liste fixe d’images prédéfinies à votre serveur :

Razor · Views/Home/Index.cshtml
var editor = new RichTextEditor("#editor", {
    toolbar: "full",
    galleryEndpoint: "/rte/gallery",
    maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
});

La boîte de dialogue ajoute ?folder= à cette URL pour lister un dossier, et envoie action=create-folder à la même URL pour le bouton New folder. Les envois passent par rte-files.js vers /rte/upload.

La route

C# · Controllers/RteController.cs
// GET /rte/gallery?folder=products
[HttpGet("gallery")]
public IActionResult Gallery([FromQuery] string? folder) =>
    Send(RteFiles.List(HttpContext, folder));

Dans l’exemple Minimal API, la même ligne est rte.MapGet("/gallery", (HttpContext http, string? folder) => ...). Les deux appellent RteFiles.List :

C# · RteFiles.cs
// 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 transforme le dossier virtuel en un chemin à l’intérieur de uploads/, ou répond 400 ou 404. Dossiers de galerie avec ASP.NET Core l’explique.

Le JSON que lit la boîte de dialogue

C# · RteFiles.cs
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);
}
Le JSON que renvoie l’exemple ASP.NET Core pour GET /rte/gallery?folder=blog : currentFolder, currentFolderDisplay, parentFolder, une liste folders vide et des images avec url, name, folder et size
La réponse réelle pour le dossier blog, réduite à deux images.
  • parentFolder vaut null à la racine, ce qui désactive le bouton Up de la boîte de dialogue. Dans un dossier, c’est le chemin virtuel du parent, "" pour un dossier de premier niveau.
  • Les propriétés sont en camelCase parce que les valeurs JSON par défaut d’ASP.NET Core sont en camelCase, aussi bien pour Results.Json que pour l'ObjectResult de MVC.
  • Seuls les cinq types d’images sont listés. Les dossiers dont le nom ne respecte pas les règles, comme .git, sont ignorés.
  • Les noms sont triés selon leur forme en minuscules, avec une comparaison ordinale. StringComparer.OrdinalIgnoreCase compare en majuscules : il classe donc a_b et aab autrement que le navigateur.
  • Chaque segment d’URL est échappé avec Uri.EscapeDataString, si bien qu’un dossier nommé My photos donne quand même une URL valide.

Servir les images

Les URL pointent vers /uploads/, servi par un second appel à UseStaticFiles avec sa propre liste de types de contenu :

C# · RteEndpoints.cs
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",
});

Cette liste a son importance. Avec le FileExtensionContentTypeProvider par défaut, un fichier .html ou .svg placé dans le dossier serait servi en text/html ou image/svg+xml. Désactiver ServeUnknownFileTypes, ce qui est le réglage par défaut, ne bloque que les extensions que le fournisseur ne connaît pas. Avec la liste de l’exemple, un probe.html et un probe.svg que nous avons placés dans uploads/ ont tous deux renvoyé 404.

Ce que fait la boîte de dialogue aujourd’hui

La boîte de dialogue de la version publiée de l’éditeur indique « Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF ». Ce serveur en accepte cinq. Un .bmp est envoyé, le serveur répond 400, et la boîte de dialogue affiche son propre message « Upload failed for pic.bmp. » plutôt que celui du serveur. Un fichier qui dépasse maxUploadFileSize est refusé dans le navigateur avec « big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB. ». La boîte de dialogue compte en mégaoctets binaires.

Tester

Lancez le projet d’exemple de ce guide avec dotnet run, ouvrez http://localhost:5302 et cliquez sur Insert Gallery. Pour voir le JSON sans la boîte de dialogue :

Shell
curl "http://localhost:5302/rte/gallery?folder=blog%2F2026"
{"currentFolder":"blog/2026","currentFolderDisplay":"/blog/2026","parentFolder":"blog","folders":[],"images":[]}

Ce dossier a été créé avec le bouton New folder. Un dossier qui n’existe pas répond 404 {"error":"Folder not found."}.

Le même guide pour d’autres plateformes

JavaScript · Next.js · PHP · Razor Pages · Blazor · MVC 5 · Web Forms

À lire ensuite