The data prop always includes antiforgeryToken and problem — an RFC 9457 validation problem ({ title, status, errors }, where errors is the standard ASP.NET Record<string, string[]> member), or null when the model state is valid. Razor Pages named handlers work as normal form actions — action="?handler=toggle" maps to OnPostToggle(int id).
Without JavaScript, forms are plain POSTs with full page reloads. Attach enhance for SvelteKit-style progressive enhancement — the submit goes over fetch and the island updates in place:
<script lang="ts">
import { enhance } from 'sveltenet/client';
let { data: serverData }: { data: IndexData } = $props();
// Writable $derived: tracks the server prop, assignable from enhanced responses.
let data = $derived(serverData);
const applyUpdate = enhance({
// A getter: `data` is replaced by each enhanced response.
token: () => data.antiforgeryToken,
onUpdate: (d: IndexData) => (data = d),
});
</script>
<form method="post" {@attach applyUpdate}>
<input name="NewLabel" />
<button>Add</button>
</form>Razor Pages validates an antiforgery token on every POST. Pass token to enhance and
it is sent as the RequestVerificationToken header, so no form needs a hidden input.
AddSvelteNet() configures AntiforgeryOptions.HeaderName for this (ASP.NET sets none
by default, and would otherwise reject the post); an explicit app setting is left alone.
A form that already renders a hidden __RequestVerificationToken input — server-rendered
markup, or an asp-antiforgery tag helper — works with no token at all: enhance picks
the value up from the form. Concurrent submits of the same form are ignored while one is
in flight, so a double-click cannot post a mutation twice.
enhance submits with an X-SvelteNet header. SveltePage answers enhanced requests with JSON instead of HTML:
- Handler returns
Page()with valid model state → the fresh props bag ({ data }). - Handler returns
Page()with invalid model state →400 application/problem+json: an RFC 9457 problem details with theerrorsmember, plus adataextension member carrying the fresh props —onUpdatestill fires, and the island reads the errors fromdata.problem. Any problem-details-aware client parses the same response. - Handler redirects (post/redirect/get) →
{ "redirect": url }; the client follows it with a second enhanced fetch and applies the fresh data (then resets the form; passreset: falseto keep values). - Anything else (JSON, files, status codes) passes through untouched.
- A plain
GETwith theX-SvelteNetheader returns the page'sdataas JSON — useful for polling or manual refreshes.
Enhanced forms degrade gracefully: no JS (or an onError you handle) means the normal POST flow. Override CreateEnhancedResult on your page model to customize the protocol. See samples/TodoApp for the full add/toggle/validation wiring.