Enviar imagens de um editor de texto rico no Blazor
No Blazor Server, é tentador mandar tudo pelo componente: o circuito já está conectado e o C# está ali do lado. Para os uploads de imagens do editor isso não funciona, e os motivos são específicos do Blazor. Esta página explica por que as imagens vão para endpoints HTTP comuns no mesmo app, como esses endpoints ficam ao lado de MapRazorComponents e o que o middleware de antiforgery do template do Blazor faz com eles.
Por que não pelo circuito
- O editor faz POST para uma URL. Arrastar e soltar, colar, a caixa de diálogo Insert Image e o botão Upload da galeria enviam todos um XHR
multipart/form-data, por meio dorte-files.js, para/rte/upload. Um componente Razor não tem uma URL que aceite um POST. - O circuito tem um limite de 32 KB por mensagem. As mensagens do navegador para o servidor pelo SignalR são limitadas a 32 KB por padrão, e isso inclui os resultados de JS interop. Retornamos o HTML do editor como string por
InvokeAsync<string>: 20,000 caracteres chegaram, 40,000 fecharam o circuito com "Connection closed with an error". Mandar os bytes das imagens por interop esbarraria no mesmo limite. O próprio<InputFile>do Blazor contorna isso com streaming, mas o editor não usaInputFile.
Por isso, o componente apenas cria o editor (Adicionar uma galeria de imagens a um editor de texto rico no Blazor mostra como), e os arquivos trafegam por HTTP, como em qualquer outro app ASP.NET Core.
Program.cs
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() mapeia três endpoints de Minimal API: GET e POST /rte/gallery e POST /rte/upload. UseRteStaticFiles() serve as imagens armazenadas em /uploads, o editor em /richtexteditor e o rte-files.js. Tudo isso roda no mesmo processo que o circuito, então os endpoints podem usar os mesmos serviços e a mesma autenticação.
O antiforgery do template
O template do Blazor Web App chama app.UseAntiforgery() para os formulários do próprio Blazor. No .NET 8, um endpoint que faz binding de IFormFile carrega metadados de antiforgery; assim, com esse middleware ativo, um upload sem token recebia um 400. Em Development, o motivo aparece: "Invalid anti-forgery token found when reading parameter "IFormFile file" from the request body as form." Os XHRs do editor não levam token, então os dois endpoints POST desativam essa verificação:
// Binding [FromForm] or IFormFile makes .NET 8 demand an antiforgery token on these endpoints.
// The editor's XHRs cannot send one, so it is switched off here and RteFiles checks the
// request's origin instead (see RequireSameOrigin). guide-notes.md has the details.
rte.MapPost("/gallery", (HttpContext http, [FromForm] string? action, [FromForm] string? folder, [FromForm] string? name) =>
Json(RteFiles.CreateFolder(http, action, folder, name)))
.DisableAntiforgery();rte.MapPost("/upload", async (HttpContext http, IFormFile? file, [FromForm] string? folder) =>
Json(await RteFiles.Save(http, file, folder)))
.DisableAntiforgery()
// Kestrel accepts 30,000,000-byte bodies by default; this route needs ~4 MB.
.WithMetadata(new RequestSizeLimitAttribute(RteFiles.MaxRequestBytes))
.WithFormOptions(multipartBodyLengthLimit: RteFiles.MaxRequestBytes);No lugar do token, RteFiles recusa POSTs que o navegador marca como vindos de outro site, usando Sec-Fetch-Site e Origin. Os formulários do Blazor mantêm a proteção deles. A rota de upload também reduz o limite de requisição para 4,065,536 bytes, contra o padrão de 30,000,000 do Kestrel; Limites de tamanho de upload no ASP.NET Core mostra o que acontece acima dele. Depois, RteFiles.Save verifica a pasta, o tamanho, a extensão e os primeiros bytes, e armazena o arquivo com um novo nome, como no guia de upload do ASP.NET Core.

Teste
Execute o projeto de exemplo deste guia com dotnet run e abra http://localhost:5304. Abra Insert Gallery, clique em Upload e escolha uma foto: ela é armazenada, listada e inserida quando você clica duas vezes nela. A partir de um shell, a mesma rota:
curl -F "file=@photo.jpg" http://localhost:5304/rte/upload
{"url":"/uploads/photo-e2f78227.jpg"}No painel de rede do navegador, a imagem é uma requisição POST /rte/upload comum, ao lado do WebSocket _blazor.
O mesmo guia para outras plataformas
JavaScript · Next.js · PHP · ASP.NET Core · Razor Pages · MVC 5 · Web Forms