Save time and keystrokes with Blazor’s comprehensive form components.
If you’re building web applications, you’re likely going to need to capture information from your users. One (still) common way to do that is to use a form. But how do you use forms in Blazor, and what changes when you switch between Blazor’s various render modes?
Here’s a quick-start guide to form handling in Blazor (plus, stick around until the end for a neat way to get your form up and running faster, with less boilerplate).
First, let’s start with the fundamentals. Here’s a basic HTML form, where someone can enter their email address.
@page "/Newsletter"
<h3>Newsletter Signup</h3>
<form method="post">
<label for="email" class="form-label">Email:</label>
<input type="text" name="email" id="email"/>
<button type="submit">Submit</button>
</form>
@code {
}
Right now, this is a standard HTML form, albeit declared inside a Razor component.
If you’re using Blazor’s static server-side rendering render mode, you can declare a form like this and capture the data when the form is submitted.
But if you submit the form, you’ll get an error.
A valid antiforgery token was not provided with the request. Add an antiforgery token, or disable antiforgery validation for this endpoint.
So what gives?
Here’s what happens when this form is submitted:
<your-app>/Newsletter
.ASP.NET uses anti-forgery tokens to help protect your app. Without them, a malicious actor could easily send dodgy data to this /newsletter page, via POST requests.
To counter this, when you declare a form in your Razor component you can include an anti-forgery token. This token is generated on the server when your component renders, and is then included in the resulting markup.
When the form is subsequently submitted, ASP.NET (running on the server) checks the provided token is legit and allows the request through if so.
We can wire up this token using Blazor’s <AntiforgeryToken />
component:
<form method="post">
<label for="email" class="form-label">Email:</label>
<input type="text" name="email" id="email"/>
<button type="submit">Submit</button>
<AntiforgeryToken />
</form>
If you run this in the browser and inspect the source code, you’ll see the generated token included as a hidden field.
<form method="post" action="/newsletter">
<label for="email" class="form-label">Email:</label>
<input type="text" name="email" id="email">
<button type="submit">Submit</button>
<input type="hidden" name="__RequestVerificationToken" value="CfDJ8GblH5ZtkwJHinTfwL5hSdkZOv0g0xvTgr7JwOxofF2PQRVB1ORm8rhTM-MhtjNn_Ob9WZ6U01VU0a734QCLT5L-klOpNbm1ON6A2t2BuZ8R1WYHDgCfzoY0k7TKlYOBkxcUNLechsHYNC0UEvv1QVU"></form>
This token will be included in the data that’s posted to your server when a user submits this form.
Anti-forgery token in place, you might expect this form to spring into life, but try submitting it at this point and you’ll get a new error.
The POST request does not specify which form is being submitted. To fix this, ensure
<form>
elements have a @formname attribute with any unique value, or pass a FormName parameter if using<EditForm>
.
This time, the request has made it past .NET’s checks (it has a valid anti-forgery token), so ASP.NET will try to figure out where to send the incoming request.
For this to work, you need to give your form a name.
<form method="post" @formname="newsletter-signup">
<label for="email" class="form-label">Email:</label>
<input type="text" name="email" id="email"/>
<button type="submit">Submit</button>
<AntiforgeryToken />
</form>
Note that we’ve used the @formname
directive to give our form a unique name. This name is then rendered in the resulting HTML as a value for the _handler
hidden field.
<form method="post" action="/newsletter">
<input type="hidden" name="_handler" value="newsletter-signup">
<!-- rest of form -->
</form>
ASP.NET, when it handles the request, will use this name to locate your component (checking for a component with a form with that name).
Success! Now when you submit this form, .NET will send the data to your component but …
Nothing happens.
Because we’ve missed one final step. We need to write the code to handle the submitted form data.
We can use the @onsubmit
directive to indicate which method should handle the posted form data.
<form method="post" @formname="newsletter-signup" @onsubmit="EmailSubmitted">
<!-- rest of form -->
</form>
Then implement the handler itself.
@code {
[SupplyParameterFromForm]
public string Email { get; set; }
public void EmailSubmitted()
{
Console.WriteLine(Email);
}
}
Here, we’ve declared a property to capture the Email address from the submitted form data, and the EmailSubmitted
method that will be invoked when a submission comes in.
The [SupplyParameterFromForm]
ensures the Email
property will be populated with the value the user entered in the form.
With that, our form is alive!
Enter an email address, submit the form and you’ll see the email address logged in the Output window of your IDE (or terminal, depending on how you launched your app locally).
Right now, when the form is submitted the email address (entered by the user) will be cleared form the text input field.
We can preserve that value by binding the value of the text input to the Email
property we declared in that last step.
<form method="post" @formname="newsletter-signup" @onsubmit="EmailSubmitted">
<label for="email" class="form-label">Email:</label>
<!-- use model binding to bind the value of this input to @Email -->
<input type="text" name="email" id="email" value="@Email"/>
<!-- rest of form -->
</form>
Here we’ve bound value
for our text input to the Email
property in our component.
Now, when the form is submitted, the text input will continue to show the entered email address.
Plain HTML forms like this work just fine with Blazor static SSR, but what if you want to cut down on some of the boilerplate and get better compile-time safety for your form?
For that, Blazor has a dedicated form component called EditForm
.
Here’s how we can declare the same form using EditForm
.
@page "/Newsletter/EditForm"
@using BlazorDemoApp.Data
<h3>Newsletter Signup</h3>
<EditForm method="post" Model="NewsletterForm" FormName="newsletter-signup" OnValidSubmit="EmailSubmitted" >
<label for="email" class="form-label">Email:</label>
<InputText id="email" @bind-Value="NewsletterForm.Email"/>
<button type="submit">Submit</button>
</EditForm>
A lot of this is the same (or very similar) to our plain HTML form.
But notice we haven’t needed to add the <AntiforgeryToken />
component, as EditForm
includes this automatically.
We’re able to use the Model
property to point to the underlying data model for our form.
Here’s how we’ve set that up for this component.
@code {
[SupplyParameterFromForm]
public NewsletterForm NewsletterForm { get; set; } = new();
public void EmailSubmitted()
{
Console.WriteLine(NewsletterForm.Email);
}
}
Here we’ve a NewsletterForm
property which is instantiated to an empty object when the component first mounts.
The NewsletterForm
model itself looks like this:
public class NewsletterForm
{
public string Email { get; set; }
}
Back in the form, in place of the input
element, we’ve declared an instance of Blazor’s InputText
component.
<InputText id="email" @bind-Value="NewsletterForm.Email"/>
Which we’re able to bind to a specific property on the model (Email
in this case).
Because we’re using static server-side rendering as the render mode, we still need that [SupplyParameterFromForm]
attribute we saw earlier if we want our NewsletterForm
property to be populated with the submitted form data.
This form works very similarly to our plain HTML form, but there are some advantages.
If we try to map an input to a non-existent property on the model, we’ll get a compile error (rather than having to wait until runtime for the problem to become evident).
We also get to avoid relying on magic strings for things like the name
of the text input.
With EditForm
, we can now easily switch render modes.
The above form will work exactly the same with the render mode set to InteractiveServer
, InteractiveWebAssembly
or InteractiveAuto
.
In those cases, you can drop the [SupplyParameterFromForm]
as it isn’t needed.
Of course, you can’t always rely on users to enter valid data for your forms. Blazor has built-in support for form validation.
The easiest option out of the gate is to use DataAnnotations
. (You can also use other libraries such as Fluent Validation.)
We can indicate when a field in our underlying model is required (and/or specify other validation criteria, for example that a number has to fall within a certain range).
using System.ComponentModel.DataAnnotations;
namespace BlazorDemoApp.Data;
public class NewsletterForm
{
[Required]
public string Email { get; set; }
}
Then bring the DataAnnotationsValidator
in to capture any issues when the form is submitted.
<EditForm method="post" Model="NewsletterForm" FormName="newsletter-signup" OnValidSubmit="EmailSubmitted">
<DataAnnotationsValidator />
<label for="email" class="form-label">Email:</label>
<InputText id="email" @bind-Value="NewsletterForm.Email"/>
<button type="submit">Submit</button>
<ValidationSummary />
</EditForm>
With this, the Email
input will be highlighted red if we omit an email address when we submit the form. We’ll also see an error message displayed at the bottom of the form.
If we had multiple inputs, and several of them failed validation, ValidationSummary
will show a summary of those validation errors.
Sometimes you may find yourself asking for the same information twice from a user.
Take an online store checkout form for example. If users have different shipping and delivery addresses, you may well want to capture the same information for both (street, city, etc.).
You can create one component for the relevant address form fields, and use that in multiple places in your form.
First, here’s a model we might use for a checkout form.
public record PlaceOrderCommand
{
public Address BillingAddress { get; set; } = new();
public Address ShippingAddress { get; set; } = new();
public List<OrderItem> Items { get; set; } = new();
public class Address
{
[Required]
public string Name { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
[Required]
public string PostCode { get; set; }
}
public class OrderItem
{
public string SKU { get; set; }
public int Quantity { get; set; }
}
}
Notice the two address fields, BillingAddress
and ShippingAddress
.
We can define a separate component for capturing address details.
@using BlazorDemoApp.Shared.Checkout
@inherits Editor<PlaceOrderCommand.Address>
<div>
<label>Name</label>
<InputText @bind-Value="Value.Name"/>
</div>
<div>
<label>Address 1</label>
<InputText @bind-Value="Value.AddressLine1"/>
</div>
<div>
<label>Address 2</label>
<InputText @bind-Value="Value.AddressLine2"/>
</div>
<div>
<label>City</label>
<InputText @bind-Value="Value.City"/>
</div>
<div>
<label>Post Code</label>
<InputText @bind-Value="Value.PostCode"/>
</div>
Note how this component inherits Editor<PlaceOrderCommand.Address>
.
We can reference this component in our main checkout form (multiple times, for multiple address fields).
<EditForm Model="Command" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
<DataAnnotationsValidator/>
<h4>Bill To:</h4>
<AddressEntry @bind-Value="Command.BillingAddress"/>
<h4>Ship To:</h4>
<AddressEntry @bind-Value="Command.ShippingAddress"/>
<button type="submit">Place Order</button>
<ValidationSummary/>
</EditForm>
This is a handy way to avoid duplication in your form, especially when capturing the exact same information multiple times.
Finally, while the EditForm
is a key component in Blazor, and will handle most scenarios, you might find yourself wanting to spin up forms without having to write so much boilerplate code.
In the earlier examples, we had to declare labels for our inputs, as well as the inputs themselves.
To control the appearance of the form, we would need to write CSS or use built-in classes in CSS frameworks like Blazor to make each field appear as we want.
Third-party form components cut a lot of this time/effort.
For example, here’s a version of the newsletter signup form (with a few extra fields) built using the Blazor Form component from Progress Telerik.
Notice we’ve ditched the label
elements, preferring Telerik’s FormItem
component instead (which will automatically render a label for each input).
Here’s how this form renders in the browser.
With this approach, the correct types of input fields are rendered automatically, based on the underlying model.
For example, the model in this case has an int
property for Age
, so we get a number input for that field.
We can arrange the form into multiple columns (as here) and easily switch the form’s orientation (vertical or horizontal).
Combine the form with Telerik Blazor MediaQuery
, and the form becomes responsive (without having to manually configure CSS to make it look right on mobile).
If you’re building an application with lots of forms, this is a solid way to get a battle-tested form up and running quickly.
Forms are vital for many web applications.
If you need to add forms to your web app you can start with simple HTML forms (for Blazor static SSR apps), but for better support in Blazor’s render modes and to reduce boiilerplate code, you can turn to Blazor’s built-in form components, such as EditForm.
Finally, if you’re building lots of forms, third-party form components such as the one included in Progress Telerik UI for Blazor can save you time (and keystrokes) by cutting down on boilerplate, and save you writing lots of CSS to get your forms to look right in the browser. Try it free for 30 days.
Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.