Adicionar uma galeria de imagens a um editor de texto rico no Blazor

O RichTextEditor é um editor JavaScript; por isso, em um Blazor Web App ele é criado e removido por JS interop, e a galeria conversa com endpoints HTTP comuns ao lado dos seus componentes. Esta página mostra as partes do Blazor: onde ficam os scripts e as opções do editor, quando o componente pode criar o editor, como removê-lo depois e como as rotas da galeria ficam em Program.cs. Ela usa a renderização Interactive Server do .NET 8.
Scripts e opções no App.razor
Os arquivos do editor, o rte-files.js compartilhado e três pequenas funções de interop ficam no <head> de App.razor, que é renderizado uma única vez e não por um componente interativo:
<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>As opções do editor, com galleryEndpoint, são escritas em JavaScript em vez de enviadas pelo C#. As opções podem conter funções, como botões personalizados e handlers de eventos, e elas não podem vir do .NET como JSON. getHtml é explicado em Ler o HTML do editor no Blazor Server.
Criar o editor
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
await JS.InvokeVoidAsync("rteBlazor.create", host);
created = true;
}A página tem @rendermode InteractiveServer, e o Blazor a pré-renderiza: a primeira renderização acontece no servidor, sem nenhum navegador conectado. Nesse momento, o JS interop não é possível. OnAfterRenderAsync só é executado quando o circuito está conectado, e firstRender garante que o editor seja criado uma vez, e não a cada nova renderização. O elemento @ref="host" na marcação é uma <div> vazia; o Blazor nunca escreve dentro dela, então não disputa o DOM com o editor.
Remover o editor
public async ValueTask DisposeAsync()
{
if (!created) return;
try { await JS.InvokeVoidAsync("rteBlazor.destroy", host); }
catch (JSDisconnectedException) { } // the browser tab is already gone
}- A flag
created. Sem ela, descartar a cópia pré-renderizada do componente lançavaInvalidOperationException: JavaScript interop calls cannot be issued at this time. JSDisconnectedExceptionocorre quando o usuário fecha a aba: o componente é descartado depois que o navegador já se foi.destroy()pode não existir. O build do editor publicado que testamos não temeditor.destroy(); por isso,rteBlazor.destroysó o chama se ele existir e, em seguida, esvazia o elemento host.
As rotas da galeria
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() adiciona GET e POST /rte/gallery e POST /rte/upload como endpoints de Minimal API. A listagem da galeria é o mesmo JSON do guia de galeria do ASP.NET Core. As rotas são mapeadas antes de MapRazorComponents, e as rotas POST chamam .DisableAntiforgery(), porque, caso contrário, o app.UseAntiforgery() do template recusaria as requisições do editor. Enviar imagens de um editor de texto rico no Blazor explica por que os arquivos não passam pelo circuito.
O que a caixa de diálogo faz hoje
A caixa de diálogo publicada lista "Accepted formats: JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF". O servidor aceita cinco deles; assim, um .bmp é enviado, recusado com um 400, e a caixa de diálogo mostra a própria mensagem "Upload failed for pic.bmp." em vez da mensagem do servidor.
Teste
Execute o projeto de exemplo deste guia com dotnet run (SDK do .NET 8), abra http://localhost:5304, aguarde a barra de ferramentas e clique em Insert Gallery. Você verá as pastas e as imagens da pasta uploads/ do app, como na imagem no topo da página. A listagem também responde a partir de um shell:
curl "http://localhost:5304/rte/gallery?folder=team"
{"currentFolder":"team","currentFolderDisplay":"/team","parentFolder":"","folders":[],"images":[]}O mesmo guia para outras plataformas
JavaScript · Next.js · PHP · ASP.NET Core · Razor Pages · MVC 5 · Web Forms