Bilder aus einem Rich-Text-Editor in Razor Pages hochladen

RichTextEditor auf der Seite des Razor-Pages-Beispiels mit einem hochgeladenen Foto einer Stadt bei Nacht im Text

In einer Razor-Pages-App gibt es rund um den Editor zwei verschiedene POSTs. Jedes Bild wird in dem Moment hochgeladen, in dem es eingefügt wird, an einen Endpoint, der seine URL zurückgibt. Später sendet das eigene Formular der Seite das fertige HTML mit diesen URLs an OnPost. Diese Seite zeigt beides und warum die beiden getrennt bleiben: Das Formular behält sein Antiforgery-Token, und das HTML bleibt klein genug für die Formularlimits von Razor Pages.

Das Formular

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" /> transportiert das HTML. Der Tag-Helper <form method="post"> fügt das versteckte Feld __RequestVerificationToken von sich aus hinzu.
  • Der Editor startet mit dem Wert des versteckten Felds, sodass die Seite nach einem POST den gespeicherten Inhalt wieder anzeigt.
  • Der Submit-Handler kopiert editor.getHTMLCode() unmittelbar vor dem Absenden des Formulars in das Feld.

Das 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] an Html bindet das Feld beim POST. Razor Pages validiert das Antiforgery-Token, bevor OnPost läuft; ein POST ohne Token erhielt ein 400. Bereinigen Sie das HTML auf dem Server, bevor Sie es speichern oder anderen Benutzern anzeigen: Was der Editor sendet, ist trotzdem eine Eingabe aus dem Browser.

Warum die Bilder zuerst hochgeladen werden

Ohne Upload-Handler bettet der Editor ein eingefügtes Bild als Base64-data:-URL in das HTML ein, also als langen Text. Ein Formularwert in Razor Pages ist durch FormOptions.ValueLengthLimit begrenzt, standardmäßig 4,194,304 Zeichen. Mit gültigem Token wurde ein Formular-POST mit 100,000 Zeichen gespeichert, und einer mit 4,300,000 Zeichen erhielt ein 400, bevor OnPost lief. Mit der Upload-Route enthält das HTML pro Bild nur eine kurze URL. Nachdem ein hochgeladenes Bild in einen leeren Editor eingefügt und auf Save geklickt wurde, meldete die Seite „Saved 108 characters of HTML.“

Die Upload-Route

rte-files.js, auf der Seite geladen, sendet jedes Bild an /rte/upload: Drag & Drop, Einfügen, den Dialog „Insert Image“ und die Upload-Schaltfläche der Galerie. Die Route ist ein Minimal-API-Endpoint, der in Program.cs mit app.MapRteFiles() abgebildet wird:

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);

Sie kann kein Page-Handler sein, weil Handler als /?handler=Upload adressiert werden und jeder POST-Handler das Antiforgery-Token verlangt, das der XHR des Editors nicht senden kann. Eine Bildergalerie für einen Rich-Text-Editor in Razor Pages erklärt diese Entscheidung. Die beiden Limits halten den Request unter 4,065,536 Bytes; Kestrel würde sonst 30,000,000 annehmen. RteFiles.Save prüft dann Origin, Ordner, Größe, Dateiendung und die ersten Bytes und speichert die Datei unter einem neuen Namen. Diese Prüfungen sind dieselben wie in der Upload-Anleitung für ASP.NET Core.

Der Dialog „Insert Gallery“ des Razor-Pages-Beispiels mit den Ordnern blog, products und team und vier hochgeladenen Fotos, eines davon ausgewählt
Die Galerie auf der Razor Page nach vier Uploads.

Testen

Starten Sie das Beispielprojekt zu dieser Anleitung mit dotnet run und öffnen Sie http://localhost:5303. Fügen Sie ein Bild aus der Galerie ein, laden Sie bei Bedarf eines über deren Upload-Schaltfläche hoch und klicken Sie auf Save: Die Meldung zeigt die Länge des HTML, und das Bild ist nach dem Neuladen der Seite noch da. In einer 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

Der Upload funktioniert ohne Token. Der bloße Formular-POST wird abgelehnt, weil er keines hat.

Dieselbe Anleitung für andere Plattformen

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

Weiterlesen