Published:

Blazor WebAssembly - Form Validation Example

Built with ASP.NET Core Blazor WebAssembly 3.2.1

Other versions available:

This is a quick example of how to setup form validation in ASP.NET Core Blazor WebAssembly. The example is a simple registration form with pretty standard fields for title, first name, last name, date of birth, email, password, confirm password and an accept terms and conditions checkbox. All fields are required including the checkbox, the dob must be a valid date, the password field must have a min length of 6, the email must be a valid email address, and the 'confirm password' must match the password.

Blazor validates inputs when they are changed or when the form is submitted. On valid submit the HandleValidSubmit() method is called which simply displays the contents of the form as a JSON object in a javascript alert.

Styling of the example is done with Bootstrap 4.5 CSS, a couple of the validation class names differ slightly between Bootstrap and Blazor, for these classes I simply copied over the styles from Bootstrap into the example app.css (/wwwroot/css/app.css) and renamed them to be compatible with Blazor.

The CSS classes that were copied from Bootstrap and renamed for Blazor are:

  • Bootstrap .invalid-feedback == Blazor .validation-message
  • Bootstrap .is-invalid == Blazor .invalid

For more info about Bootstrap see https://getbootstrap.com/docs/4.5/getting-started/introduction/.

Here it is in action:(Hosted on GitHub Pages at https://cornflourblue.github.io/blazor-webassembly-form-validation/)


Blazor Form Validation Component

Path: /Pages/Index.razor

The home page component contains our example sign up form which is built using the ASP.NET Core <EditForm> component and some of the built-in form input components (<InputSelect>, <InputText>, <InputDate> and <InputCheckbox>).

The editContext object that is bound to the form is used to initialize the form with an empty model object in the OnInitialized() method. The editContext is also used to reset the form in the HandleReset() method.

The <DataAnnotationsValidator /> component enables support for validating the form using the data annotations attributes on the Model class that is bound to the form (e.g. [Required], [EmailAddress]), and the <ValidationMessage For="..." /> components display the validation message below each field.

The Model class contains properties for each of the fields in the form along with validation rules defined using data annotations attributes. A couple of the validation attributes have custom error messages because the default wasn't very clear, and the [Display(Name = "...")] is used on a few attributes to add spaces between the field name in validation error messages.

For more info ASP.NET Core Blazor forms and validation see https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation.

@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Text.Json
@inject IJSRuntime JSRuntime;

<div class="card m-3">
    <h4 class="card-header">Blazor WebAssembly Form Validation Example</h4>
    <div class="card-body">
        <EditForm EditContext="@editContext" OnValidSubmit="HandleValidSubmit" @onreset="HandleReset">
            <DataAnnotationsValidator />
            <div class="form-row">
                <div class="form-group col">
                    <label>Title</label>
                    <InputSelect @bind-Value="model.Title" class="form-control">
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    </InputSelect>
                    <ValidationMessage For="@(() => model.Title)" />
                </div>
                <div class="form-group col-5">
                    <label>First Name</label>
                    <InputText @bind-Value="model.FirstName" class="form-control" />
                    <ValidationMessage For="@(() => model.FirstName)" />
                </div>
                <div class="form-group col-5">
                    <label>Last Name</label>
                    <InputText @bind-Value="model.LastName" class="form-control" />
                    <ValidationMessage For="@(() => model.LastName)" />
                </div>
            </div>
            <div class="form-row">
                <div class="form-group col">
                    <label>Date of Birth</label>
                    <InputDate @bind-Value="model.DateOfBirth" class="form-control" />
                    <ValidationMessage For="@(() => model.DateOfBirth)" />
                </div>
                <div class="form-group col">
                    <label>Email</label>
                    <InputText @bind-Value="model.Email" class="form-control" />
                    <ValidationMessage For="@(() => model.Email)" />
                </div>
            </div>
            <div class="form-row">
                <div class="form-group col">
                    <label>Password</label>
                    <InputText @bind-Value="model.Password" type="password" class="form-control" />
                    <ValidationMessage For="@(() => model.Password)" />
                </div>
                <div class="form-group col">
                    <label>Confirm Password</label>
                    <InputText @bind-Value="model.ConfirmPassword" type="password" class="form-control" />
                    <ValidationMessage For="@(() => model.ConfirmPassword)" />
                </div>
            </div>
            <div class="form-group form-check">
                <InputCheckbox @bind-Value="model.AcceptTerms" id="acceptTerms" class="form-check-input" />
                <label for="acceptTerms" class="form-check-label">Accept Terms & Conditions</label>
                <ValidationMessage For="@(() => model.AcceptTerms)" />
            </div>
            <div class="text-center">
                <button type="submit" class="btn btn-primary mr-1">Register</button>
                <button type="reset" class="btn btn-secondary">Cancel</button>
            </div>
        </EditForm>
    </div>
</div>

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

    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
    }

    private void HandleValidSubmit()
    {
        var modelJson = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
        JSRuntime.InvokeVoidAsync("alert", $"SUCCESS!! :-)\n\n{modelJson}");
    }

    private void HandleReset()
    {
        model = new Model();
        editContext = new EditContext(model);
    }

    private class Model
    {
        [Required]
        public string Title { get; set; }

        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required]
        [Display(Name = "Date of Birth")]
        public DateTime? DateOfBirth { get; set; }

        [Required]
        [EmailAddress]
        public string Email { get; set; }

        [Required]
        [MinLength(6, ErrorMessage = "Password must be at least 6 characters")]
        public string Password { get; set; }

        [Required]
        [CompareProperty("Password")]
        [Display(Name = "Confirm Password")]
        public string ConfirmPassword { get; set; }

        [Required]
        [Range(typeof(bool), "true", "true", ErrorMessage = "Accept Ts & Cs is required")]
        public bool AcceptTerms { get; set; }
    }
}


App CSS with renamed Boostrap validation classes

Path: /wwwroot/css/app.css

These are the validation classes in the example app.css that were copied from Bootstrap and renamed to their equivalent Blazor classes to make the validation messages appear correctly with Bootstrap styling.

/* 
    below styles were copied from bootstrap 4.5.0 and renamed to be compatible with blazor
    - '.validation-message' == bootstrap '.invalid-feedback'
    - '.invalid' == bootstrap '.is-invalid'
*/

.validation-message {
    display: none;
    width: 100%;
    margin-top: 0.25rem;
    font-size: 80%;
    color: #dc3545;
}

.invalid ~ .validation-message {
    display: block;
}

.form-control.invalid {
    border-color: #dc3545;
    padding-right: calc(1.5em + 0.75rem);
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: right calc(0.375em + 0.1875rem) center;
    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}

.form-control.invalid:focus {
    border-color: #dc3545;
    box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}

textarea.form-control.invalid {
    padding-right: calc(1.5em + 0.75rem);
    background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
}

.form-check-input.invalid ~ .form-check-label {
    color: #dc3545;
}

.form-check-input.invalid ~ .validation-message {
    display: block;
}

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by