Enviar imagens de um editor de texto rico no Razor Pages

O RichTextEditor na página do exemplo em Razor Pages com uma foto enviada de uma cidade à noite inserida no texto

Em um aplicativo Razor Pages, há dois POSTs diferentes em torno do editor. Cada imagem é enviada no momento em que é inserida, para um endpoint que retorna a URL dela. Depois, o formulário da própria página envia o HTML final, com essas URLs, para OnPost. Esta página mostra os dois e por que eles ficam separados: o formulário mantém o token antiforgery, e o HTML fica pequeno o bastante para os limites de formulário do Razor Pages.

O formulário

Razor · Pages/Index.cshtml
<form method="post" id="post">
    <input type="hidden" asp-for="Html" />
    <div id="editor"></div>
    <p><button type="submit">Save</button> @Model.Message</p>
</form>
<script>
    var editor = new RichTextEditor("#editor", {
        toolbar: "full",
        galleryEndpoint: "/rte/gallery",
        maxUploadFileSize: 4000000 // same limit as the server, checked before the upload starts
    });
    var html = document.getElementById("Html");
    editor.setHTMLCode(html.value);
    document.getElementById("post").addEventListener("submit", function () {
        html.value = editor.getHTMLCode();
    });
</script>
  • <input type="hidden" asp-for="Html" /> leva o HTML. A tag helper <form method="post"> adiciona sozinha o campo oculto __RequestVerificationToken.
  • O editor começa com o valor do campo oculto, então, depois de um POST, a página mostra de novo o conteúdo salvo.
  • O handler de submit copia editor.getHTMLCode() para o campo logo antes de o formulário ser enviado.

O page model

C# · Pages/Index.cshtml.cs
// Razor Pages validates the antiforgery token on this POST by default; the <form> tag helper
// in Index.cshtml adds the hidden __RequestVerificationToken field. Save Html to your database here.
public void OnPost() => Message = $"Saved {Html?.Length ?? 0} characters of HTML.";

[BindProperty] em Html faz o binding do campo no POST. O Razor Pages valida o token antiforgery antes de OnPost rodar; um POST sem ele recebeu um 400. Sanitize o HTML no servidor antes de armazená-lo ou exibi-lo a outros usuários: o que o editor envia continua sendo entrada vinda do navegador.

Por que as imagens são enviadas primeiro

Sem um handler de upload, o editor incorpora uma imagem colada no HTML como uma URL data: em base64, que é um texto longo. Um valor de formulário do Razor Pages é limitado por FormOptions.ValueLengthLimit, 4,194,304 caracteres por padrão. Com um token válido, um POST de formulário de 100,000 caracteres foi salvo, e um de 4,300,000 caracteres recebeu um 400 antes de OnPost rodar. Com a rota de upload, o HTML guarda apenas uma URL curta por imagem. Depois de inserir uma imagem enviada em um editor vazio e clicar em Save, a página informou "Saved 108 characters of HTML."

A rota de upload

O rte-files.js, carregado na página, envia todas as imagens para /rte/upload: arrastar e soltar, colar, a caixa de diálogo Insert Image e o botão Upload da galeria. A rota é um endpoint de Minimal API mapeado em Program.cs com app.MapRteFiles():

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

Ela não pode ser um handler de página, porque handlers são acessados como /?handler=Upload e todo handler POST exige o token antiforgery, que a XHR do editor não consegue enviar. Adicionar uma galeria de imagens a um editor de texto rico no Razor Pages explica essa escolha. Os dois limites mantêm a requisição abaixo de 4,065,536 bytes; caso contrário, o Kestrel aceitaria 30,000,000. Em seguida, RteFiles.Save verifica a origem, a pasta, o tamanho, a extensão e os primeiros bytes, e armazena o arquivo com um novo nome. Essas verificações são as mesmas do guia de upload do ASP.NET Core.

A caixa de diálogo Insert Gallery do exemplo Razor Pages listando as pastas blog, products e team e quatro fotos enviadas, uma delas selecionada
A galeria na Razor Page, depois de quatro uploads.

Teste

Execute o projeto de exemplo deste guia com dotnet run e abra http://localhost:5303. Insira uma imagem da galeria, envie uma com o botão Upload dela se quiser, e clique em Save: a mensagem mostra o tamanho do HTML, e a imagem continua lá depois que a página recarrega. Em um shell:

Shell
curl -F "file=@photo.jpg" http://localhost:5303/rte/upload
{"url":"/uploads/photo-4cdaeb3e.jpg"}

curl -s -o /dev/null -w "%{http_code}\n" -d "Html=x" http://localhost:5303/
400

O upload funciona sem token. O POST de formulário puro é recusado, porque não tem nenhum.

O mesmo guia para outras plataformas

JavaScript · Next.js · PHP · ASP.NET Core · Blazor · MVC 5 · Web Forms

Leia a seguir