Home C# / .NET Mastering Blazor Forms and Validation: A Complete Guide
Intermediate 3 min · July 13, 2026

Mastering Blazor Forms and Validation: A Complete Guide

Learn to build robust forms in Blazor with built-in validation, custom validators, and real-world patterns.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of C# and .NET
  • Familiarity with Blazor components
  • Understanding of HTML and CSS
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Blazor forms use EditForm component with DataAnnotationsValidator for model validation. You can create custom validators, handle form submission, and display validation messages using ValidationMessage and ValidationSummary. Key components: EditForm, InputText, ValidationMessage, DataAnnotationsValidator.

✦ Definition~90s read
What is Blazor Forms and Validation?

Blazor forms and validation is a system for building interactive forms with client-side and server-side validation using data annotations and custom logic.

Think of Blazor forms like a digital paper form with a smart assistant.
Plain-English First

Think of Blazor forms like a digital paper form with a smart assistant. The EditForm is the clipboard, input components are fillable fields, and validators are rules that check if you've filled everything correctly before submitting. The assistant highlights mistakes in real-time, just like a spell-checker but for form data.

Forms are the backbone of user interaction in web applications. Whether it's a login page, a registration form, or a complex data entry screen, collecting and validating user input is critical. Blazor, Microsoft's modern web framework, provides a powerful and intuitive system for building forms with built-in validation. In this tutorial, you'll learn how to leverage Blazor's EditForm component, integrate data annotations, create custom validation logic, and handle form submission like a pro. We'll cover real-world scenarios, common pitfalls, and production debugging techniques to ensure your forms are robust and user-friendly. By the end, you'll be able to build forms that provide immediate feedback, prevent invalid data, and enhance the overall user experience.

Setting Up a Basic Blazor Form

To create a form in Blazor, you use the EditForm component. This component manages the form's lifecycle, including validation and submission. Start by defining a model class with data annotation attributes for validation. Then, in your Blazor component, wrap your input fields in an EditForm and bind them to the model properties using @bind-Value. Add a DataAnnotationsValidator to enable validation based on the attributes. Finally, handle the OnValidSubmit event to process the form when all validations pass. Here's a simple example:

BasicForm.razorCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@page "/basic-form"
@using System.ComponentModel.DataAnnotations

<h3>Basic Form</h3>

<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <div>
        <label for="name">Name:</label>
        <InputText id="name" @bind-Value="@model.Name" />
        <ValidationMessage For="@(() => model.Name)" />
    </div>
    <div>
        <label for="email">Email:</label>
        <InputText id="email" @bind-Value="@model.Email" />
        <ValidationMessage For="@(() => model.Email)" />
    </div>
    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel model = new ExampleModel();

    private void HandleValidSubmit()
    {
        // Process the form
        Console.WriteLine($"Name: {model.Name}, Email: {model.Email}");
    }

    public class ExampleModel
    {
        [Required(ErrorMessage = "Name is required")]
        [StringLength(100, ErrorMessage = "Name is too long")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Email is required")]
        [EmailAddress(ErrorMessage = "Invalid email format")]
        public string Email { get; set; }
    }
}
💡Always use @bind-Value
📊 Production Insight
In production, always validate on the server as well. Client-side validation can be bypassed.
🎯 Key Takeaway
Basic Blazor forms use EditForm, DataAnnotationsValidator, and input components with @bind-Value for two-way binding.

Built-in Validation Attributes

Blazor leverages data annotations from System.ComponentModel.DataAnnotations for validation. Common attributes include [Required], [StringLength], [Range], [EmailAddress], [Compare], and [RegularExpression]. You can apply these to your model properties to enforce rules. When the form is submitted, the DataAnnotationsValidator checks these rules and populates validation messages. You can customize error messages using the ErrorMessage parameter. For example:

ValidationAttributes.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserRegistrationModel
{
    [Required(ErrorMessage = "Username is required")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
    public string Username { get; set; }

    [Required(ErrorMessage = "Password is required")]
    [StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters")]
    public string Password { get; set; }

    [Compare("Password", ErrorMessage = "Passwords do not match")]
    public string ConfirmPassword { get; set; }

    [Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
    public int Age { get; set; }

    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; }

    [RegularExpression(@"^\d{3}-\d{2}-\d{4}$", ErrorMessage = "SSN must be in format XXX-XX-XXXX")]
    public string SSN { get; set; }
}
🔥Validation works on the server too
📊 Production Insight
Avoid exposing sensitive error messages in production. Use generic messages and log detailed errors server-side.
🎯 Key Takeaway
Use data annotation attributes to define validation rules directly on your model properties.

Custom Validation Attributes

When built-in attributes are insufficient, you can create custom validation attributes by inheriting from ValidationAttribute. Override the IsValid method to implement your logic. You can also access other properties of the model using ValidationContext. For example, a custom validator that checks if a date is in the future:

FutureDateAttribute.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.ComponentModel.DataAnnotations;

public class FutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value is DateTime date)
        {
            if (date <= DateTime.Now)
            {
                return new ValidationResult("Date must be in the future.");
            }
        }
        return ValidationResult.Success;
    }
}

// Usage in model:
public class EventModel
{
    [FutureDate(ErrorMessage = "Event date must be in the future")]
    public DateTime EventDate { get; set; }
}
⚠ Custom validators run on server only by default
📊 Production Insight
For performance, avoid heavy operations in custom validators. They run on every validation request.
🎯 Key Takeaway
Create custom validation attributes by extending ValidationAttribute to implement complex validation logic.

Form Submission and Error Handling

Blazor's EditForm provides events for handling submission: OnValidSubmit, OnInvalidSubmit, and OnSubmit. Use OnValidSubmit to process the form when validation passes, and OnInvalidSubmit to handle errors. You can also use OnSubmit for custom logic before validation. To display validation errors, use ValidationSummary to show all errors in one place, or ValidationMessage for field-specific errors. Example:

FormSubmission.razorCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label for="name">Name:</label>
        <InputText id="name" @bind-Value="@model.Name" />
        <ValidationMessage For="@(() => model.Name)" />
    </div>
    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel model = new ExampleModel();

    private void HandleValidSubmit()
    {
        // Process valid form
    }

    private void HandleInvalidSubmit()
    {
        // Log or show additional messages
        Console.WriteLine("Form has validation errors.");
    }
}
💡Use OnSubmit for custom validation
📊 Production Insight
Always log invalid submission attempts for security monitoring (e.g., brute force attacks).
🎯 Key Takeaway
Handle form submission with OnValidSubmit and OnInvalidSubmit events to process valid data or show errors.

Advanced Validation: Cross-Field and Async

Sometimes validation depends on multiple fields or requires asynchronous checks (e.g., checking username availability). For cross-field validation, you can use the IValidatableObject interface on your model. Implement the Validate method to return validation errors. For async validation, you can call an API during form submission and add errors manually using EditContext. Example:

CrossFieldValidation.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System.ComponentModel.DataAnnotations;

public class PasswordModel : IValidatableObject
{
    [Required]
    public string Password { get; set; }

    [Required]
    public string ConfirmPassword { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Password != ConfirmPassword)
        {
            yield return new ValidationResult("Passwords do not match.", new[] { nameof(ConfirmPassword) });
        }
    }
}

// Async validation example in component:
private async Task HandleValidSubmit(EditContext editContext)
{
    var isValid = await CheckUsernameAvailability(model.Username);
    if (!isValid)
    {
        editContext.AddValidationMessage("Username", "Username already taken.");
        return;
    }
    // Process form
}
🔥IValidatableObject runs after data annotations
📊 Production Insight
For async checks like username availability, debounce the request to avoid excessive API calls.
🎯 Key Takeaway
Use IValidatableObject for cross-field validation and manual EditContext methods for async validation.

Styling Validation Messages

Blazor applies CSS classes to input components based on validation state: valid, invalid, and modified. You can customize the appearance of validation messages using CSS. The ValidationMessage component renders a span with the class 'validation-message'. You can also use the FieldState to apply custom styles. Example:

ValidationStyling.razorCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<style>
    .validation-message {
        color: red;
        font-size: 0.8em;
    }
    .invalid {
        border: 1px solid red;
    }
    .valid {
        border: 1px solid green;
    }
</style>

<EditForm Model="@model">
    <DataAnnotationsValidator />
    <div>
        <InputText @bind-Value="@model.Name" class="@(editContext.Field(nameof(model.Name)).IsModified ? (editContext.Field(nameof(model.Name)).IsValid ? "valid" : "invalid") : "")" />
        <ValidationMessage For="@(() => model.Name)" />
    </div>
</EditForm>

@code {
    private ExampleModel model = new ExampleModel();
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
    }
}
💡Use EditContext for advanced styling
📊 Production Insight
Ensure your CSS doesn't conflict with other styles. Use scoped CSS in Blazor components.
🎯 Key Takeaway
Customize validation UI using CSS classes applied by Blazor and the EditContext.

Security Considerations

Client-side validation is for user experience only; always validate on the server. Blazor Server apps have an additional layer because code runs on the server, but you should still validate all input. For Blazor WebAssembly, client-side validation can be bypassed. Use anti-forgery tokens to prevent CSRF attacks. Sanitize user input to avoid XSS. Example of server-side validation in a handler:

ServerValidation.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
// In a Blazor Server or WebAssembly with a server API
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] UserRegistrationModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    // Process registration
    return Ok();
}
⚠ Never trust client-side validation
📊 Production Insight
Implement rate limiting on form submission endpoints to prevent abuse.
🎯 Key Takeaway
Always perform server-side validation and use anti-forgery tokens to secure your forms.
● Production incidentPOST-MORTEMseverity: high

The Silent Form Submission: When Validation Fails Silently

Symptom
Users reported that after submitting a form, some fields were empty in the database, but no validation errors were shown.
Assumption
Developers assumed the validation was working correctly because no errors appeared.
Root cause
The form used a custom InputSelect component that didn't properly bind to the model property, so the value was always null. The validation rules were defined but never triggered because the model property remained unchanged.
Fix
Replaced the custom component with Blazor's built-in InputSelect and ensured proper two-way binding using @bind-Value.
Key lesson
  • Always test form submission with invalid data to ensure validation fires.
  • Use built-in Blazor input components when possible to avoid binding issues.
  • Log form model state on submission to catch silent failures.
  • Add client-side and server-side validation for defense in depth.
  • Monitor form submissions in production to detect anomalies.
Production debug guideSymptom to Action3 entries
Symptom · 01
Form submits without validation errors but data is missing
Fix
Check model binding: ensure @bind-Value is used on all input components. Log the model state before submission.
Symptom · 02
Validation errors not showing on the UI
Fix
Verify that ValidationMessage components are placed for each field and that the EditForm has a DataAnnotationsValidator.
Symptom · 03
Custom validator not being called
Fix
Ensure the custom validation attribute is applied correctly and that the model is being validated. Check for exceptions in the validator.
★ Quick Debug Cheat SheetCommon Blazor form issues and immediate actions
Validation not firing
Immediate action
Add DataAnnotationsValidator inside EditForm
Commands
Check EditForm markup for <DataAnnotationsValidator />
Ensure model properties have validation attributes
Fix now
Add missing validator component
Model property not updating+
Immediate action
Use @bind-Value instead of @bind
Commands
Replace @bind="model.Property" with @bind-Value="model.Property"
Check for typos in property names
Fix now
Correct binding syntax
Custom validation not showing error+
Immediate action
Check ValidationMessage for the field
Commands
Add <ValidationMessage For="@(() => model.Property)" />
Ensure custom attribute returns ValidationResult with member names
Fix now
Add ValidationMessage component
FeatureBlazor ServerBlazor WebAssembly
Validation locationServer-side (with client-side UI)Client-side (JavaScript interop)
SecurityInherently more secure (code on server)Requires server API validation
PerformanceLower latency for validationHigher latency if server round-trip needed
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
BasicForm.razor@page "/basic-form"Setting Up a Basic Blazor Form
ValidationAttributes.cspublic class UserRegistrationModelBuilt-in Validation Attributes
FutureDateAttribute.csusing System.ComponentModel.DataAnnotations;Custom Validation Attributes
FormSubmission.razorForm Submission and Error Handling
CrossFieldValidation.csusing System.ComponentModel.DataAnnotations;Advanced Validation
ValidationStyling.razor