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.
Errors (0)
@using System.Text.RegularExpressions @using System.ComponentModel.DataAnnotations <MudGrid> <MudItem xs="12" sm="7"> <MudPaper Class="pa-4"> <MudForm @ref="form" @bind-IsValid="" @bind-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 Value="@("Personal")">Personal</MudRadio> <MudRadio Value="@("Professional")">Professional</MudRadio> </MudRadioGroup> </div> <div class="d-flex align-center justify-space-between"> <MudCheckBox T="bool" Required="true" RequiredError="You must agree" Label="I agree!" /> <MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!success)" Class="ml-auto">Register</MudButton> </div> </MudForm> </MudPaper> <MudPaper Class="pa-4 mt-4"> <MudButton Variant="Variant.Filled" Color="Color.Primary" DropShadow="false" OnClick="@(()=>form.Validate())">Validate</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Secondary" DropShadow="false" OnClick="@(()=>form.ResetAsync())" Class="mx-2">Reset</MudButton> <MudButton Variant="Variant.Filled" DropShadow="false" OnClick="@(()=>form.ResetValidation())">Reset Validation</MudButton> </MudPaper> </MudItem> <MudItem xs="12" sm="5"> <MudPaper Class="pa-4 mud-height-full"> <MudText Typo="Typo.subtitle2">@($"Errors ({errors.Length})")</MudText> @foreach (var error in errors) { <MudText Color="@Color.Error">@error</MudText> } </MudPaper> </MudItem> </MudGrid>
@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.
@using System.ComponentModel.DataAnnotations <EditForm Model="" OnValidSubmit="OnValidSubmit"> <DataAnnotationsValidator/> <MudGrid> <MudItem xs="12" sm="7"> <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> </MudItem> <MudItem xs="12" sm="5"> <MudPaper Class="pa-4 mud-height-full"> <MudText Typo="Typo.subtitle2">Validation Summary</MudText> @if (success) { <MudText Color="Color.Success">Success</MudText> } else { <MudText Color="@Color.Error"> <ValidationSummary /> </MudText> } </MudPaper> </MudItem> <MudItem xs="12"> <MudText Typo="Typo.body2" Align="Align.Center"> Fill out the form correctly to see the success message. </MudText> </MudItem> </MudGrid> </EditForm>
@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 <MudPaper Class="pa-4"> <MudForm> <MudTextField @bind-Value="creditCardNr" Validation="@ccValidator.Validation" Immediate="true" Label="Credit card nr" /> </MudForm> </MudPaper>
@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.
@using FluentValidation <MudCard> <MudForm Model="" @ref="" Validation="@(orderValidator.ValidateValue)" ValidationDelay="0"> <MudCardContent> <MudTextField @bind-Value="model.Name" For="@(() => model.Name)" Immediate="true" Label="Name" /> <MudTextField @bind-Value="model.Email" For="@(() => model.Email)" Immediate="true" Label="Email" /> <MudTextField @bind-Value="model.CCNumber" For="@(() => model.CCNumber)" Immediate="true" Label="Credit card nr" /> <MudTextField @bind-Value="model.Address.Address" For="@(() => model.Address.Address)" Immediate="true" Label="Address" /> <MudTextField @bind-Value="model.Address.City" For="@(() => model.Address.City)" Immediate="true" Label="City" /> <MudTextField @bind-Value="model.Address.Country" For="@(() => model.Address.Country)" Immediate="true" Label="Country" /> </MudCardContent> <MudCardContent Class="pa-0"> <MudTable Items="@model.OrderDetails" Hover="true" Breakpoint="Breakpoint.None" Dense="" Elevation="0"> <HeaderContent> <MudTh>Description</MudTh> <MudTh>Offer</MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Description"> <MudForm Model="" Validation=@(orderDetailsValidator.ValidateValue)> <MudTextField Label="Enter Description" @bind-Value="context.Description" For="(() => context.Description)" /> </MudForm> </MudTd> <MudTd DataLabel="Offer"> <MudForm Model=""> <MudNumericField Label="Enter Offer" @bind-Value="context.Offer" Validation=@(orderDetailsValidator.ValidateValue) For="(() => context.Offer)" /> </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>
@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("Submitted!"); } } /// <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); }; } }
Validate only on user interaction
Normally input fields are validated when leaving the input field. This behavior can be changed with the flag OnlyValidateIfDirty
If this flag is set to true
, the input field will only be validated if the user has changed the initial value.
OnlyValidateIfDirty="false"
(default behavior)
OnlyValidateIfDirty="true"
<MudGrid> <MudItem xs="12" sm="6"> <MudPaper Class="pa-4"> <MudText><CodeInline>OnlyValidateIfDirty="false"</CodeInline> (default behavior)</MudText> <MudForm > <MudTextField T="string" Label="Username" Required="true" RequiredError="User name is required!"/> </MudForm> </MudPaper> </MudItem> <MudItem xs="12" sm="6"> <MudPaper Class="pa-4"> <MudText><CodeInline>OnlyValidateIfDirty="true"</CodeInline></MudText> <MudForm > <MudTextField T="string" Label="Username" Required="true" RequiredError="User name is required!" OnlyValidateIfDirty="true"/> </MudForm> </MudPaper> </MudItem> </MudGrid>
Automatically set Labels
A form field Label
can be set automatically by adding a [Label]
attribute to the relevant property on the model class. The For
property must also be set. An automatic label can be overridden by setting the Label
property manually on the component.
@using System.ComponentModel.DataAnnotations <MudCard> <MudCardContent> <MudForm Spacing="2"> <MudDatePicker For="@(() => model.Date)" /> <MudDatePicker For="@(() => model.Date)" Label="Parameter Label" /> <MudSwitch For="@(() => model.Boolean)" Color="Color.Primary" /> <MudSwitch For="@(() => model.Boolean)" Label="Parameter Label" Color="Color.Primary" /> <MudTextField For="@(() => model.String)" /> <MudTextField For="@(() => model.String)" Label="Parameter Label" /> </MudForm> </MudCardContent> </MudCard>
@code { DisplayNameLabelClass model = new(); public class DisplayNameLabelClass { [Label("Date LabelAttribute")] public DateTime? Date { get; set; } [Label("Boolean LabelAttribute")] public bool Boolean { get; set; } [Label("String LabelAttribute")] public string String { get; set; } } }
ReadOnly and Disabled Forms
Setting ReadOnly
or Disabled
to true on a MudForm will automatically set all child inputs as ReadOnly or Disabled. The state will also be applied to child MudForms.
ReadOnly Form
Nested Form
Disabled Form
Nested Form
<MudStack Row> <MudCard> <MudCardContent> <MudText Align="Align.Center">ReadOnly Form</MudText> <MudForm ReadOnly="ReadOnly" Spacing="2"> <MudTextField T="string" Label="Text" /> <MudNumericField T="int" Label="Number" /> <MudDatePicker Label="Date" /> <MudSwitch T="bool" Label="Switch" /> <MudCheckBox T="bool" Label="CheckBox" /> <MudRadioGroup T="string"> <MudRadio Value="@("1")">1</MudRadio> <MudRadio Value="@("2")">2</MudRadio> </MudRadioGroup> <MudForm ReadOnly="NestedReadOnly" Spacing="2"> <MudText Align="Align.Center">Nested Form</MudText> <MudTextField T="string" Label="Nested Text" /> <MudNumericField T="int" Label="Nested Number" /> </MudForm> </MudForm> </MudCardContent> <MudCardActions> <MudCheckBox @bind-Value="ReadOnly">ReadOnly</MudCheckBox> <MudCheckBox @bind-Value="NestedReadOnly">Nested</MudCheckBox> </MudCardActions> </MudCard> <MudCard> <MudCardContent> <MudText Align="Align.Center">Disabled Form</MudText> <MudForm Disabled="Disabled" Spacing="2"> <MudTextField T="string" Label="Text" /> <MudNumericField T="int" Label="Number" /> <MudDatePicker Label="Date" /> <MudSwitch T="bool" Label="Switch" /> <MudCheckBox T="bool" Label="CheckBox" /> <MudRadioGroup T="string"> <MudRadio Value="@("1")">1</MudRadio> <MudRadio Value="@("2")">2</MudRadio> </MudRadioGroup> <MudForm Disabled="NestedDisabled" Spacing="2"> <MudText Align="Align.Center">Nested Form</MudText> <MudTextField T="string" Label="Nested Text" /> <MudNumericField T="int" Label="Nested Number" /> </MudForm> </MudForm> </MudCardContent> <MudCardActions> <MudCheckBox @bind-Value="Disabled">Disabled</MudCheckBox> <MudCheckBox @bind-Value="NestedDisabled">Nested</MudCheckBox> </MudCardActions> </MudCard> </MudStack>
@code { bool ReadOnly = true; bool NestedReadOnly; bool Disabled = true; bool NestedDisabled; }
Customization
You can control the gap between items with Spacing
.
@using System.Text.RegularExpressions @using System.ComponentModel.DataAnnotations <MudGrid> <MudItem xs="12" sm="7"> <MudPaper Class="pa-4"> <MudForm Spacing="spacing"> <MudTextField T="string" Label="Username" /> <MudTextField T="string" Label="Email" /> <MudTextField T="string" Label="Password" HelperText="Choose a strong password" InputType="InputType.Password" /> <MudTextField T="string" Label="Password" HelperText="Repeat the password" InputType="InputType.Password" /> <div class="d-flex"> <MudRadioGroup T="string"> <MudRadio Value="@("Personal")">Personal</MudRadio> <MudRadio Value="@("Professional")">Professional</MudRadio> </MudRadioGroup> </div> </MudForm> </MudPaper> </MudItem> <MudItem xs="12" sm="5"> <MudPaper Class="pa-4 mud-height-full"> <MudSlider @bind-Value="spacing" Min="0" Max="16" ValueLabel>Spacing between items</MudSlider> </MudPaper> </MudItem> </MudGrid>
@code { int spacing; }