Add an image gallery to a rich text editor in Razor Pages

The Insert Gallery dialog of the Razor Pages sample listing the blog, products and team folders and four uploaded photos, one selected

In a Razor Pages app the natural first idea for the editor's image gallery is a set of named handlers on the page, such as OnGetGallery and OnPostUpload. For RichTextEditor's gallery they are the wrong tool, for two reasons that are specific to Razor Pages. This page shows what to do instead: the editor on a normal Razor Page, and the three gallery routes as endpoints in the same app.

Why not page handlers

  • The URLs don't fit. A named handler is reached as /?handler=Upload. The editor's upload script posts to <base>/upload, and the gallery adds ?folder= to galleryEndpoint for every listing, so handler URLs would need changes in both.
  • Every POST handler checks an antiforgery token. That is a Razor Pages default and a good one. The editor's upload and New folder requests are XHRs that the editor builds itself, and the current release cannot add the token to them. Handlers would need [IgnoreAntiforgeryToken] on the page model, which also turns the check off for the page's own Save form.

Endpoints keep the two apart: the page and its form keep full protection, and the JSON routes make their own explicit choice.

Program.cs

C# · Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();

app.UseStaticFiles();
app.UseRteStaticFiles();   // /uploads, /richtexteditor, /rte-files.js

app.MapRazorPages();       // "/" -> Pages/Index.cshtml; its own form POST keeps antiforgery validation
app.MapRteFiles();         // GET+POST /rte/gallery, POST /rte/upload

app.Run();

MapRteFiles() is the same group of Minimal API endpoints the other ASP.NET Core samples use: GET and POST /rte/gallery and POST /rte/upload. UseRteStaticFiles() serves /uploads (only the five image types), the editor at /richtexteditor and /rte-files.js.

The page

Razor · Pages/Index.cshtml
<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>

The editor is created further down the page with galleryEndpoint: "/rte/gallery". Upload images from a rich text editor in Razor Pages shows that part together with the form.

The listing route

C# · RteEndpoints.cs
rte.MapGet("/gallery", (HttpContext http, string? folder) =>
    Json(RteFiles.List(http, folder)));

The answer is the folder as JSON: currentFolder, parentFolder (null at the root), the subfolders and the images with URL, name and size. It is the same JSON and the same code as in the ASP.NET Core gallery guide, which explains each field.

The POST routes and antiforgery

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

Here is the second Razor Pages detail. AddRazorPages() registers the antiforgery services, but in .NET 8 it does not add the antiforgery middleware that Minimal API endpoints use. An endpoint that binds IFormFile or [FromForm] still carries antiforgery metadata, so without .DisableAntiforgery() every POST to it failed with a 500: "contains anti-forgery metadata, but a middleware was not found that supports anti-forgery". Adding app.UseAntiforgery() instead turns that into a 400, because the editor sends no token. Disabling it on these two routes, and checking the request's origin in their place, is what makes them work. Secure image upload in Razor Pages explains that check.

Test it

Run the sample project for this guide with dotnet run (.NET 8 SDK), open http://localhost:5303 and click Insert Gallery. The dialog lists the folders and pictures under uploads/. From a shell, the upload route answers without a token, while the page's own POST does not:

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

The 400 is Razor Pages refusing a form post without its antiforgery token, which is what you want for the page.

The same guide for other platforms

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

Read next