Adicionar uma galeria de imagens a um editor de texto rico no Razor Pages

A caixa de diálogo Insert Gallery do exemplo em Razor Pages listando as pastas blog, products e team e quatro fotos enviadas, uma delas selecionada

Em um aplicativo Razor Pages, a primeira ideia natural para a galeria de imagens do editor é um conjunto de handlers nomeados na página, como OnGetGallery e OnPostUpload. Para a galeria do RichTextEditor, eles são a ferramenta errada, por dois motivos específicos do Razor Pages. Esta página mostra o que fazer no lugar deles: o editor em uma Razor Page comum e as três rotas da galeria como endpoints no mesmo aplicativo.

Por que não usar handlers de página

  • As URLs não se encaixam. Um handler nomeado é acessado como /?handler=Upload. O script de upload do editor faz POST para <base>/upload, e a galeria acrescenta ?folder= a galleryEndpoint em cada listagem, então URLs de handler exigiriam mudanças nos dois.
  • Todo handler POST verifica um token antiforgery. Esse é um padrão do Razor Pages, e um bom padrão. As requisições de upload e de New folder do editor são XHRs que o próprio editor monta, e a versão atual não consegue acrescentar o token a elas. Os handlers precisariam de [IgnoreAntiforgeryToken] no page model, o que também desativa a verificação para o formulário Save da própria página.

Os endpoints mantêm as duas coisas separadas: a página e o formulário dela mantêm a proteção completa, e as rotas JSON fazem a própria escolha explícita.

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() é o mesmo grupo de endpoints de Minimal API que os outros exemplos ASP.NET Core usam: GET e POST /rte/gallery e POST /rte/upload. UseRteStaticFiles() serve /uploads (apenas os cinco tipos de imagem), o editor em /richtexteditor e /rte-files.js.

A página

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>

O editor é criado mais abaixo na página com galleryEndpoint: "/rte/gallery". Enviar imagens de um editor de texto rico no Razor Pages mostra essa parte junto com o formulário.

A rota de listagem

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

A resposta é a pasta como JSON: currentFolder, parentFolder (null na raiz), as subpastas e as imagens com URL, nome e tamanho. É o mesmo JSON e o mesmo código do guia de galeria do ASP.NET Core, que explica cada campo.

As rotas POST e o 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();

Aqui está o segundo detalhe do Razor Pages. AddRazorPages() registra os serviços de antiforgery, mas no .NET 8 não adiciona o middleware de antiforgery que os endpoints de Minimal API usam. Um endpoint que faz binding de IFormFile ou [FromForm] ainda carrega metadados de antiforgery, então, sem .DisableAntiforgery(), todo POST para ele falhava com um 500: "contains anti-forgery metadata, but a middleware was not found that supports anti-forgery". Adicionar app.UseAntiforgery() em vez disso transforma o erro em um 400, porque o editor não envia token. Desativá-lo nessas duas rotas e verificar a origem da requisição no lugar dele é o que as faz funcionar. Upload seguro de imagens no Razor Pages explica essa verificação.

Teste

Execute o projeto de exemplo deste guia com dotnet run (SDK do .NET 8), abra http://localhost:5303 e clique em Insert Gallery. A caixa de diálogo lista as pastas e imagens em uploads/. Em um shell, a rota de upload responde sem token, enquanto o POST da própria página não:

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 400 é o Razor Pages recusando um POST de formulário sem o token antiforgery, que é exatamente o que você quer para a página.

O mesmo guia para outras plataformas

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

Leia a seguir