Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

A form's post fails or arrives empty

Give each form a FormName, put the same name on its model's [SupplyParameterFromForm], and render the form when the post comes in.

Problem

A form works on an interactive page, but on a static page submitting it goes wrong in one of three ways:

  • The post fails with 400 Bad Request: "The POST request does not specify which form is being submitted."
  • The post succeeds, but the fields come back empty and validation says required fields are missing, though they were filled in.
  • The post fails with 400 Bad Request: "Cannot submit the form 'profile' because no form on the page currently has that name."

In production the 400 has no message; run in Development to see it.

Cause

On a static page, a post is a new request. Blazor renders the page again, finds the form the post names, and fills the model from the posted fields. Each step goes by name:

  • No FormName. The form posts no name, so Blazor can't tell which form was submitted. An interactive page doesn't post, so it never needed one.
  • Different names. [SupplyParameterFromForm(FormName = "...")] fills the model only from the form with that name. With another name, the model isn't filled and the page creates an empty one.
  • The form isn't there. The form is shown only after something the post doesn't carry, such as a field set by an earlier post, so the page Blazor renders for the post has no form with that name.

Fix

Name the form, and give its model the same name. A page with two forms gives each its own name, and each post fills only its own model:

Profile.razor
<Form Model="Model" FormName="profile" OnValidSubmit="SaveAsync">
    <TextField @bind-Value="Model!.Name" />
    <Button Submit>Save</Button>
</Form>

@code {
    [SupplyParameterFromForm(FormName = "profile")]
    private ProfileModel? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}

Preview

Code

TwoForms.razor
@using System.ComponentModel.DataAnnotations

<Stack Gap="8" MaxWidth="28.Rem" Grow>
    <Form Model="Search" FormName="docs-search" OnValidSubmit="() => _searched = true">
        <TextField @bind-Value="Search!.Query" Type="TextFieldType.Search" />
        <Row Gap="3" Align="Align.Center" Wrap>
            <Button Submit Variant="ButtonVariant.Outline">Search</Button>
            @if (_searched)
            {
                <TextBlock Size="TextSize.Sm" Color="Color.Muted">Searching for "@Search.Query".</TextBlock>
            }
        </Row>
    </Form>

    <Form Model="Signup" FormName="newsletter-signup" OnValidSubmit="() => _subscribed = true">
        <TextField @bind-Value="Signup!.Email" AutoComplete="email" />
        <Row Gap="3" Align="Align.Center" Wrap>
            <Button Submit>Subscribe</Button>
            @if (_subscribed)
            {
                <TextBlock Size="TextSize.Sm" Color="Color.Muted">Subscribed @Signup.Email.</TextBlock>
            }
        </Row>
    </Form>
</Stack>

@code {
    // Each form's FormName, and the same name on its model: a post fills only its own form's model.
    [SupplyParameterFromForm(FormName = "docs-search")]
    private SearchModel? Search { get; set; }

    [SupplyParameterFromForm(FormName = "newsletter-signup")]
    private SignupModel? Signup { get; set; }

    private bool _searched;
    private bool _subscribed;

    protected override void OnInitialized()
    {
        Search ??= new();
        Signup ??= new();
    }

    public sealed class SearchModel
    {
        [Required, Display(Name = "Search the docs")]
        public string? Query { get; set; }
    }

    public sealed class SignupModel
    {
        [Required, EmailAddress]
        public string? Email { get; set; }
    }
}

Off: Each post is a new request that fills only its own form's model: after Subscribe, the search is empty again.

When a form appears only in some states, keep that state where the post can see it: in the address (the form posts back to the same address, query string included), or in the posted fields. Without FormName, [SupplyParameterFromForm] takes a post from any form on the page, so name it whenever the page has more than one.