Ajouter une galerie d’images à un éditeur de texte enrichi avec Blazor

RichTextEditor est un éditeur JavaScript : dans une Blazor Web App, il est donc créé et supprimé via l’interopérabilité JS, et la galerie communique avec des points de terminaison HTTP ordinaires, à côté de vos composants. Cette page présente les parties propres à Blazor : où placer les scripts et les options de l’éditeur, quand le composant peut créer l’éditeur, comment le supprimer ensuite, et comment les routes de la galerie s’intègrent à Program.cs. Elle utilise le rendu Interactive Server de .NET 8.
Scripts et options dans App.razor
Les fichiers de l’éditeur, le script partagé rte-files.js et trois petites fonctions d’interopérabilité vont dans le <head> de App.razor, qui est rendu une seule fois et pas par un composant interactif :
<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>Les options de l’éditeur, avec galleryEndpoint, sont écrites en JavaScript plutôt qu’envoyées depuis C#. Les options peuvent contenir des fonctions, comme des boutons personnalisés et des gestionnaires d’événements, et celles-ci ne peuvent pas transiter depuis .NET en JSON. getHtml est expliqué dans Lire le HTML de l’éditeur avec Blazor Server.
Créer l’éditeur
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
await JS.InvokeVoidAsync("rteBlazor.create", host);
created = true;
}La page porte @rendermode InteractiveServer, et Blazor la prérend : le premier rendu a lieu sur le serveur, sans navigateur connecté. L’interopérabilité JS est alors impossible. OnAfterRenderAsync ne s’exécute qu’une fois le circuit connecté, et firstRender garantit que l’éditeur est créé une seule fois, et non à chaque nouveau rendu. L’élément @ref="host" du balisage est un <div> vide ; Blazor n’écrit jamais à l’intérieur, et n’entre donc pas en conflit avec l’éditeur sur le DOM.
Le supprimer
public async ValueTask DisposeAsync()
{
if (!created) return;
try { await JS.InvokeVoidAsync("rteBlazor.destroy", host); }
catch (JSDisconnectedException) { } // the browser tab is already gone
}- L’indicateur
created. Sans lui, la libération de la copie prérendue du composant levaitInvalidOperationException: JavaScript interop calls cannot be issued at this time. JSDisconnectedExceptionsurvient quand l’utilisateur ferme l’onglet : le composant est libéré après la disparition du navigateur.destroy()peut être absent. La version publiée de l’éditeur que nous avons testée n’a pas d'editor.destroy():rteBlazor.destroyne l’appelle donc que s’il existe, puis vide l’élément hôte.
Les routes de la galerie
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() ajoute GET et POST /rte/gallery ainsi que POST /rte/upload sous forme de points de terminaison Minimal API. La liste de la galerie est le même JSON que dans le guide sur la galerie avec ASP.NET Core. Les routes sont déclarées avant MapRazorComponents, et les routes POST appellent .DisableAntiforgery(), car sinon le app.UseAntiforgery() du modèle de projet refuserait les requêtes de l’éditeur. Téléverser des images depuis un éditeur de texte enrichi avec Blazor explique pourquoi les fichiers ne passent pas par le circuit.
Ce que fait la boîte de dialogue aujourd’hui
La boîte de dialogue de la version publiée indique « Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF ». Le serveur en accepte cinq : un .bmp est donc envoyé, refusé avec un 400, et la boîte de dialogue affiche son propre message « Upload failed for pic.bmp. » au lieu de celui du serveur.
Tester
Lancez le projet d’exemple de ce guide avec dotnet run (SDK .NET 8), ouvrez http://localhost:5304, attendez la barre d’outils et cliquez sur Insert Gallery. Vous voyez les dossiers et les images du dossier uploads/ de l’application, comme sur l’image en haut de la page. La liste répond aussi depuis un shell :
curl "http://localhost:5304/rte/gallery?folder=team"
{"currentFolder":"team","currentFolderDisplay":"/team","parentFolder":"","folders":[],"images":[]}Le même guide pour d’autres plateformes
JavaScript · Next.js · PHP · ASP.NET Core · Razor Pages · MVC 5 · Web Forms