Subir imágenes desde un editor de texto enriquecido en Razor Pages

RichTextEditor en la página del ejemplo de Razor Pages con una foto subida de una ciudad de noche insertada en el texto

En una aplicación Razor Pages hay dos envíos distintos alrededor del editor. Cada imagen se sube en el momento en que se inserta, a un endpoint que devuelve su URL. Más tarde, el formulario de la propia página envía el HTML terminado, con esas URL dentro, a OnPost. Esta página muestra ambos y por qué se mantienen separados: el formulario conserva su token antiforgery y el HTML sigue siendo lo bastante pequeño para los límites de formulario de Razor Pages.

El formulario

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" /> lleva el HTML. El tag helper <form method="post"> añade por sí solo el campo oculto __RequestVerificationToken.
  • El editor parte del valor del campo oculto, así que después de un envío la página vuelve a mostrar el contenido guardado.
  • El controlador del evento submit copia editor.getHTMLCode() en el campo justo antes de enviar el formulario.

El modelo de página

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] sobre Html enlaza el campo en el POST. Razor Pages valida el token antiforgery antes de que se ejecute OnPost; un envío sin él recibió un 400. Sanea el HTML en el servidor antes de guardarlo o de mostrarlo a otros usuarios: lo que envía el editor sigue siendo una entrada del navegador.

Por qué las imágenes se suben primero

Sin un controlador de carga, el editor incrusta una imagen pegada en el HTML como una URL data: en base64, que es un texto largo. Un valor de formulario de Razor Pages está limitado por FormOptions.ValueLengthLimit, 4,194,304 caracteres de forma predeterminada. Con un token válido, un envío de formulario de 100,000 caracteres se guardó, y uno de 4,300,000 caracteres recibió un 400 antes de que se ejecutara OnPost. Con la ruta de carga, el HTML solo contiene una URL corta por imagen. Después de insertar una imagen subida en un editor vacío y hacer clic en Save, la página informó "Saved 108 characters of HTML."

La ruta de carga

rte-files.js, cargado en la página, envía cada imagen a /rte/upload: arrastrar y soltar, pegar, el cuadro de diálogo Insert Image y el botón Upload de la galería. La ruta es un endpoint de Minimal API asignado en Program.cs con 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);

No puede ser un page handler, porque los handlers se direccionan como /?handler=Upload y cada handler POST exige el token antiforgery, que el XHR del editor no puede enviar. Añadir una galería de imágenes a un editor de texto enriquecido en Razor Pages explica esa decisión. Los dos límites mantienen la solicitud por debajo de 4,065,536 bytes; de lo contrario, Kestrel aceptaría 30,000,000. Después, RteFiles.Save comprueba el origen, la carpeta, el tamaño, la extensión y los primeros bytes, y guarda el archivo con un nombre nuevo. Esas comprobaciones son las mismas que en la guía de carga para ASP.NET Core.

El cuadro de diálogo Insert Gallery del ejemplo de Razor Pages con las carpetas blog, products y team y cuatro fotos subidas, una de ellas seleccionada
La galería en la Razor Page, después de cuatro cargas.

Pruébalo

Ejecuta el proyecto de ejemplo de esta guía con dotnet run y abre http://localhost:5303. Inserta una imagen desde la galería, sube una con su botón Upload si quieres y haz clic en Save: el mensaje muestra la longitud del HTML, y la imagen sigue ahí después de recargar la página. Desde un 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

La carga funciona sin token. El envío simple del formulario se rechaza, porque no lleva ninguno.

La misma guía para otras plataformas

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

Para seguir leyendo