Añadir una galería de imágenes a un editor de texto enriquecido en Blazor

RichTextEditor es un editor de JavaScript, así que en una Blazor Web App se crea y se elimina mediante JS interop, y la galería se comunica con endpoints HTTP normales junto a tus componentes. Esta página muestra las partes de Blazor: dónde van los scripts y las opciones del editor, cuándo puede crear el editor el componente, cómo volver a eliminarlo y cómo encajan las rutas de la galería en Program.cs. Usa el renderizado Interactive Server de .NET 8.
Scripts y opciones en App.razor
Los archivos del editor, el rte-files.js compartido y tres pequeñas funciones de interop van en el <head> de App.razor, que se renderiza una sola vez y no lo renderiza un componente interactivo:
<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>Las opciones del editor, con galleryEndpoint, se escriben en JavaScript en lugar de enviarse desde C#. Las opciones pueden contener funciones, como botones personalizados y controladores de eventos, y esas no pueden viajar desde .NET como JSON. getHtml se explica en Leer el HTML del editor en Blazor Server.
Crear el editor
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
await JS.InvokeVoidAsync("rteBlazor.create", host);
created = true;
}La página tiene @rendermode InteractiveServer, y Blazor la prerrenderiza: el primer renderizado ocurre en el servidor, sin ningún navegador conectado. En ese momento no es posible usar JS interop. OnAfterRenderAsync solo se ejecuta cuando el circuito está conectado, y firstRender garantiza que el editor se cree una vez y no en cada nuevo renderizado. El elemento @ref="host" del marcado es un <div> vacío; Blazor nunca escribe dentro de él, así que no compite con el editor por el DOM.
Eliminarlo
public async ValueTask DisposeAsync()
{
if (!created) return;
try { await JS.InvokeVoidAsync("rteBlazor.destroy", host); }
catch (JSDisconnectedException) { } // the browser tab is already gone
}- El indicador
created. Sin él, al desechar la copia prerrenderizada del componente se lanzabaInvalidOperationException: JavaScript interop calls cannot be issued at this time. JSDisconnectedExceptionocurre cuando el usuario cierra la pestaña: el componente se desecha cuando el navegador ya no está.- Puede que
destroy()no exista. La versión publicada del editor que probamos no tieneeditor.destroy(), así querteBlazor.destroysolo lo llama si existe y después vacía el elemento host.
Las rutas de la galería
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() añade GET y POST /rte/gallery y POST /rte/upload como endpoints de Minimal API. El listado de la galería es el mismo JSON que en la guía de la galería para ASP.NET Core. Las rutas se asignan antes de MapRazorComponents, y las rutas POST llaman a .DisableAntiforgery(), porque de lo contrario el app.UseAntiforgery() de la plantilla rechazaría las solicitudes del editor. Subir imágenes desde un editor de texto enriquecido en Blazor explica por qué los archivos no pasan por el circuito.
Qué hace hoy el cuadro de diálogo
El cuadro de diálogo publicado indica "Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF". El servidor acepta cinco de ellos, así que un .bmp se envía, se rechaza con un 400 y el cuadro de diálogo muestra su propio "Upload failed for pic.bmp." en lugar del mensaje del servidor.
Pruébalo
Ejecuta el proyecto de ejemplo de esta guía con dotnet run (SDK de .NET 8), abre http://localhost:5304, espera a que aparezca la barra de herramientas y haz clic en Insert Gallery. Verás las carpetas y las imágenes de la carpeta uploads/ de la aplicación, como en la imagen del principio. El listado también responde desde un shell:
curl "http://localhost:5304/rte/gallery?folder=team"
{"currentFolder":"team","currentFolderDisplay":"/team","parentFolder":"","folders":[],"images":[]}La misma guía para otras plataformas
JavaScript · Next.js · PHP · ASP.NET Core · Razor Pages · MVC 5 · Web Forms