MudBlazor

Form Validation

All about checking user input and visualization of errors.

Simple Form Validation

MudForm is designed to be easy and simple. You just pass your own validation functions directly into the Validation parameter of your input controls. But if you want to make use of the handy data annotation attributes provided by Microsoft, you can pass them into Validation, as well. You can even use FluentValidation as shown in one of the examples below.

Choose a strong password

Repeat the password

Show Errors (0)
@using System.Text.RegularExpressions
@using System.ComponentModel.DataAnnotations


<div style="max-width: 400px;">
    <MudCard>
        <MudCardContent>
            <MudForm @ref="form" @bind-IsValid="@success" @bind-Errors="@errors">
                <MudTextField T="string" Label="Username" Required="true" RequiredError="User name is required!"/>
                <MudTextField T="string" Label="Email" Required="true" RequiredError="Email is required!"
                              Validation="@(new EmailAddressAttribute() {ErrorMessage = "The email address is invalid"})"/>
                <MudTextField T="string" Label="Password" HelperText="Choose a strong password" @ref="pwField1"
                              InputType="InputType.Password"
                              Validation="@(new Func<string, IEnumerable<string>>(PasswordStrength))" Required="true"
                              RequiredError="Password is required!"/>
                <MudTextField T="string"
                              Label="Password" HelperText="Repeat the password" InputType="InputType.Password"
                              Validation="@(new Func<string, string>(PasswordMatch))"/>
                <div class="d-flex">
                    <MudRadioGroup T="string" Required="true" RequiredError="Account type is required!">
                        <MudRadio Option="@("Personal")">Personal</MudRadio>
                        <MudRadio Option="@("Professional")">Professional</MudRadio>
                    </MudRadioGroup>
                </div>
                <MudCheckBox T="bool" Required="true" RequiredError="You must agree" Class="ml-n2"
                             Label="I agree that MudBlazor is awesome!"/>
            </MudForm>
        </MudCardContent>
        <MudCardActions>
            <MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!success)" Class="ml-auto">Register</MudButton>
        </MudCardActions>
    </MudCard>
    
    <MudPaper Class="pa-4 justify-center my-4 mud-text-align-center">
        <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(()=>form.Validate())">Validate</MudButton>
        <MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(()=>form.Reset())" Class="mx-2">Reset</MudButton>
        <MudButton Variant="Variant.Filled" OnClick="@(()=>form.ResetValidation())">Reset Validation</MudButton>
    </MudPaper>

    <MudExpansionPanels>
        <MudExpansionPanel Text="@($"Show Errors ({errors.Length})")">
            @foreach (var error in errors)
            {
                <MudText Color="@Color.Error">@error</MudText>
            }
        </MudExpansionPanel>
    </MudExpansionPanels>
</div>
@code {
    bool success;
    string[] errors = { };
    MudTextField<string> pwField1;
    MudForm form;

    private IEnumerable<string> PasswordStrength(string pw)
    {
        if (string.IsNullOrWhiteSpace(pw))
        {
            yield return "Password is required!";
            yield break;
        }
        if (pw.Length < 8)
            yield return "Password must be at least of length 8";
        if (!Regex.IsMatch(pw, @"[A-Z]"))
            yield return "Password must contain at least one capital letter";
        if (!Regex.IsMatch(pw, @"[a-z]"))
            yield return "Password must contain at least one lowercase letter";
        if (!Regex.IsMatch(pw, @"[0-9]"))
            yield return "Password must contain at least one digit";
    }

    private string PasswordMatch(string arg)
    {
        if (pwField1.Value != arg)
            return "Passwords don't match";
        return null;
    }

}

EditForm Support

In Blazor, form validation is usually done with EditForm in conjunction with a form model class that is decorated with data annotations. MudBlazor's input components support Blazor's form validation if you put them into a <EditForm>. The following example shows a very simple use case. If you want to learn more, please check out ASP.NET Core Blazor forms and validation on the official Blazor documentation.

Max. 8 characters

Choose a strong password

Repeat the password

Fill out the form correctly to see the success message.

Show Validation Summary

@using System.ComponentModel.DataAnnotations

<div style="max-width: 400px;">
    <EditForm Model="@model" OnValidSubmit="OnValidSubmit">
        <DataAnnotationsValidator />
        <MudCard>
            <MudCardContent>
                <MudTextField Label="First name" HelperText="Max. 8 characters"
                              @bind-Value="model.Username" For="@(() => model.Username)" />
                <MudTextField Label="Email" Class="mt-3"
                              @bind-Value="model.Email" For="@(() => model.Email)" />
                <MudTextField Label="Password" HelperText="Choose a strong password" Class="mt-3"
                              @bind-Value="model.Password" For="@(() => model.Password)" InputType="InputType.Password" />
                <MudTextField Label="Password" HelperText="Repeat the password" Class="mt-3"
                              @bind-Value="model.Password2" For="@(() => model.Password2)" InputType="InputType.Password" />
            </MudCardContent>
            <MudCardActions>
                <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto">Register</MudButton>
            </MudCardActions>
        </MudCard>
        <MudText Typo="Typo.body2" Align="Align.Center" Class="my-4">
            Fill out the form correctly to see the success message.
        </MudText>

        <MudExpansionPanels>
            <MudExpansionPanel Text="Show Validation Summary">
                @if (success)
                {
                    <MudText Color="Color.Success">Success</MudText>
                }
                else
                {
                    <MudText Color="@Color.Error">
                        <ValidationSummary />
                    </MudText>
                }
            </MudExpansionPanel>
        </MudExpansionPanels>
    </EditForm>
</div>
@code {
    RegisterAccountForm model = new RegisterAccountForm();
    bool success;

    public class RegisterAccountForm
    {
        [Required]
        [StringLength(8, ErrorMessage = "Name length can't be more than 8.")]
        public string Username { get; set; }

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

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

        [Required]
        [Compare(nameof(Password))]
        public string Password2 { get; set; }

    }

    private void OnValidSubmit(EditContext context)
    {
        success = true;
        StateHasChanged();
    }

}

Using Simple Fluent Validation

This example shows how to make use of the powerful FluentValidation validators with MudForm. Basically, we call the validator in our validation function and simply return the error messages.

@using FluentValidation

<div style="max-width: 400px;">
    <MudCard>
        <MudCardContent>
            <MudForm>
                <MudTextField @bind-Value="creditCardNr"
                              Validation="@ccValidator.Validation"
                              Immediate="true"
                              Label="Credit card nr" />
            </MudForm>
        </MudCardContent>
    </MudCard>
</div>
@code { 
    // This is a valid Visa test card number
    string creditCardNr = "4012 8888 8888 1881";

    // The validation rules (overkill, I know, but very fluent):
    FluentValueValidator<string> ccValidator = new FluentValueValidator<string>(x => x
        .NotEmpty()
        .Length(1,100)
        .CreditCard());

    /// <summary>
    /// A glue class to make it easy to define validation rules for single values using FluentValidation
    /// You can reuse this class for all your fields, like for the credit card rules above.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class FluentValueValidator<T> : AbstractValidator<T>
    {
        public FluentValueValidator(Action<IRuleBuilderInitial<T, T>> rule)
        {
            rule(RuleFor(x => x));
        }

        private IEnumerable<string> ValidateValue(T arg)
        {
            var result = Validate(arg);
            if (result.IsValid)
                return new string[0];
            return result.Errors.Select(e => e.ErrorMessage);
        }

        public Func<T, IEnumerable<string>> Validation => ValidateValue;
    }
}

Using Fluent Validation

This example shows how to make use of the powerful FluentValidation validators with MudForm. This approach utilizes a standard AbstractValidator which can be shared with the ASP.NET API Controllers.

Description Offer
@using FluentValidation

<div style="max-width: 400px;">
    <MudCard>
		<MudForm Model="@model" @ref="@form">
			<MudCardContent>

					<MudTextField @bind-Value="model.Name"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.Name)"
								  Immediate="true"
								  Label="Name" />

					<MudTextField @bind-Value="model.Email"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.Email)"
								  Immediate="true"
								  Label="Email" />

					<MudTextField @bind-Value="model.CCNumber"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.CCNumber)"
								  Immediate="true"
								  Label="Credit card nr" />

					<MudTextField @bind-Value="model.Address.Address"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.Address.Address)"
								  Immediate="true"
								  Label="Address" />

					<MudTextField @bind-Value="model.Address.City"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.Address.City)"
								  Immediate="true"
								  Label="City" />

					<MudTextField @bind-Value="model.Address.Country"
								  Validation="@(orderValidator.ValidateValue)"
								  For="@(() => model.Address.Country)"
								  Immediate="true"
								  Label="Country" />

			</MudCardContent>
			<MudCardContent>

				<MudTable Items="@model.OrderDetails" Hover="true" Breakpoint="Breakpoint.None" Dense="@true">
					<HeaderContent>
						<MudTh>Description</MudTh>
						<MudTh>Offer</MudTh>
					</HeaderContent>
					<RowTemplate>
						<MudTd DataLabel="Description">
							<MudForm Model="@context">
  								<MudTextField Label="Enter Description" @bind-Value="context.Description" For="(() => context.Description)" Validation=@(orderDetailsValidator.ValidateValue) />
  							</MudForm>
						</MudTd>
						<MudTd DataLabel="Offer">
							<MudForm Model="@context">
  								<MudNumericField Label="Enter Offer" @bind-Value="context.Offer" For="(() => context.Offer)" Validation=@(orderDetailsValidator.ValidateValue) />
  							</MudForm>
						</MudTd>
					</RowTemplate>
				</MudTable>

			</MudCardContent>
		</MudForm>
        <MudCardActions>
            <MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="@(async () => await Submit())">Order</MudButton>
        </MudCardActions>
    </MudCard>
</div>
@code {
	[Inject] ISnackbar Snackbar { get; set; }

    MudForm form;

    OrderModelFluentValidator orderValidator = new OrderModelFluentValidator();

	OrderDetailsModelFluentValidator orderDetailsValidator = new OrderDetailsModelFluentValidator();

    OrderModel model = new OrderModel();

    public class OrderModel
    {
        public string Name { get; set; }
        public string Email { get; set; }
        public string CCNumber { get; set; } = "4012 8888 8888 1881";
        public AddressModel Address { get; set; } = new AddressModel();
		public List<OrderDetailsModel> OrderDetails = new List<OrderDetailsModel>()
		{
			new OrderDetailsModel()
				{
					Description = "Perform Work order 1",
					Offer = 100
				},
			new OrderDetailsModel()
		};
	}

    public class AddressModel
    {
        public string Address { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
    }

	public class OrderDetailsModel
	{
		public string Description { get; set; }
		public decimal Offer { get; set; }
	}

    private async Task Submit()
    {
        await form.Validate();

        if (form.IsValid)
        {
            Snackbar.Add("Submited!");
        }
    }

    /// <summary>
    /// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
    /// </summary>
    /// <typeparam name="OrderModel"></typeparam>
    public class OrderModelFluentValidator : AbstractValidator<OrderModel>
    {
        public OrderModelFluentValidator()
        {
            RuleFor(x => x.Name)
                .NotEmpty()
                .Length(1,100);

            RuleFor(x => x.Email)
                .Cascade(CascadeMode.Stop)
                .NotEmpty()
                .EmailAddress()
                .MustAsync(async (value, cancellationToken) => await IsUniqueAsync(value));

            RuleFor(x => x.CCNumber)
                .NotEmpty()
                .Length(1,100)
                .CreditCard();

            RuleFor(x => x.Address.Address)
                .NotEmpty()
                .Length(1,100);

            RuleFor(x => x.Address.City)
                .NotEmpty()
                .Length(1,100);

            RuleFor(x => x.Address.Country)
                .NotEmpty()
                .Length(1,100);

			RuleForEach(x => x.OrderDetails)
				.SetValidator(new OrderDetailsModelFluentValidator());
		}

        private async Task<bool> IsUniqueAsync(string email)
        {
            // Simulates a long running http call
            await Task.Delay(2000);
            return email.ToLower() != "test@test.com";
        }

        public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
        {
            var result = await ValidateAsync(ValidationContext<OrderModel>.CreateWithOptions((OrderModel)model, x => x.IncludeProperties(propertyName)));
            if (result.IsValid)
                return Array.Empty<string>();
            return result.Errors.Select(e => e.ErrorMessage);
        };
    }

	/// <summary>
    /// A standard AbstractValidator for the Collection Object
    /// </summary>
    /// <typeparam name="OrderDetailsModel"></typeparam>
    public class OrderDetailsModelFluentValidator : AbstractValidator<OrderDetailsModel>
    {
        public OrderDetailsModelFluentValidator()
        {
            RuleFor(x => x.Description)
                .NotEmpty()
                .Length(1,100);

			RuleFor(x => x.Offer)
				.GreaterThan(0)
				.LessThan(999);
        }

        public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
        {
            var result = await ValidateAsync(ValidationContext<OrderDetailsModel>.CreateWithOptions((OrderDetailsModel)model, x => x.IncludeProperties(propertyName)));
            if (result.IsValid)
                return Array.Empty<string>();
            return result.Errors.Select(e => e.ErrorMessage);
        };
    }
}
An error has occurred. This application may no longer respond until reloaded. Reload 🗙