Blazor EditForm validation only partially works when using a 'Validator component' (within a 'business logic validation' function)
See original GitHub issueDescribe the bug
The issue is I’m not able to get a validation message to show against a field. Although the error message does appear in the validation summary.

This issue is also further restricted to only complex model properties. The approach of using “ValidateComplexType”, “ObjectGraphDataAnnotationsValidator” to resolve complex model property validation limitation does not help in this instance.
To Reproduce
The sample code below which I have created to demonstrate the problem is virtually ALL code from the Microsoft docs website (https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation). I have attached the complete source code solution. Simply rebuild the solution and run it to test. BlazorTest1.zip
Index.razor
@page "/"
@using System.ComponentModel.DataAnnotations;
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<CustomValidator @ref="customValidator" />
<ValidationSummary />
@*<ObjectGraphDataAnnotationsValidator />*@ @* does not work!*@
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" disabled="@disabled" />
<ValidationMessage For="@(() => starship.Identifier)" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" disabled="@disabled" />
<ValidationMessage For="@(() => starship.Description)" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification" disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
<ValidationMessage For="@(() => starship.Classification)" />
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" disabled="@disabled" />
<ValidationMessage For="@(() => starship.MaximumAccommodation)" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" disabled="@disabled" />
<ValidationMessage For="@(() => starship.IsValidatedDesign)" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" disabled="@disabled" />
<ValidationMessage For="@(() => starship.ProductionDate)" />
</label>
</p>
<p>
<strong>Captain's Name</strong>
</p>
<p>
<label>
Firstname:
<InputText @bind-Value="starship.CaptainsName.Firstname" disabled="@disabled" />
<ValidationMessage For="@(() => starship.CaptainsName.Firstname)" />
</label>
</p>
<p>
<label>
Lastname:
<InputText @bind-Value="starship.CaptainsName.Lastname" disabled="@disabled" />
<ValidationMessage For="@(() => starship.CaptainsName.Lastname)" />
</label>
</p>
<button type="submit" disabled="@disabled">Submit</button>
<p style="@messageStyles">
@message
</p>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private bool disabled;
private string message;
private string messageStyles = "visibility:hidden";
private CustomValidator customValidator;
private Starship starship = new Starship() { ProductionDate = DateTime.UtcNow };
protected override void OnInitialized()
{
base.OnInitialized();
customValidator = new CustomValidator();
starship = new Starship()
{
ProductionDate = DateTime.UtcNow,
Identifier = "1",
Classification = "Defense",
MaximumAccommodation = 100,
IsValidatedDesign = true,
CaptainsName = new PersonsName { Firstname = "John", Lastname = "Kirk" }
};
}
private async Task HandleValidSubmit(EditContext editContext)
{
bool isValid = editContext.Validate(); // Data Annotations validation
if (isValid)
{
BusinessLogicValidation();
}
}
private bool BusinessLogicValidation()
{
customValidator.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new List<string>() { "For a 'Defense' ship classification, 'Description' is required." });
}
if (starship.CaptainsName.Firstname != "James")
{
//errors.Add(nameof(starship.CaptainsName.Firstname), new List<string>() { "Firstname must be James" }); // does not work (as expected)!
errors.Add("CaptainsName.Firstname", new List<string>() { "Firstname must be James" });
}
if (errors.Count() > 0)
{
customValidator.DisplayErrors(errors);
return true;
}
return false;
}
}
Starship.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace BlazorTest1.Client.Shared
{
public class Starship
{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
public string Identifier { get; set; }
public string Description { get; set; }
[Required]
public string Classification { get; set; }
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
public int MaximumAccommodation { get; set; }
[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
// [ValidateComplexType] // does not work!
public PersonsName CaptainsName { get; set; }
}
}
PersonsName.cs
namespace BlazorTest1.Client.Shared
{
public class PersonsName
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
}
Further technical details
-
.NET 5
-
The output of
dotnet --info
.NET SDK (reflecting any global.json): Version: 5.0.200 Commit: 70b3e65d53
Runtime Environment: OS Name: Windows OS Version: 10.0.19042 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\5.0.200\
Host (useful for support): Version: 5.0.3 Commit: c636bbdc8a
.NET SDKs installed: 2.1.202 [C:\Program Files\dotnet\sdk] 2.1.521 [C:\Program Files\dotnet\sdk] 3.1.100-preview2-014569 [C:\Program Files\dotnet\sdk] 3.1.200-preview-014883 [C:\Program Files\dotnet\sdk] 3.1.300 [C:\Program Files\dotnet\sdk] 3.1.406 [C:\Program Files\dotnet\sdk] 5.0.100-rc.1.20452.10 [C:\Program Files\dotnet\sdk] 5.0.102 [C:\Program Files\dotnet\sdk] 5.0.103 [C:\Program Files\dotnet\sdk] 5.0.200-preview.20601.7 [C:\Program Files\dotnet\sdk] 5.0.200-preview.21077.7 [C:\Program Files\dotnet\sdk] 5.0.200 [C:\Program Files\dotnet\sdk]
.NET runtimes installed: Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0-preview7.19365.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0-rc1.19457.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.0-preview2.19528.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.22 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0-rc1-19456-20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.0-preview2.19525.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0-preview7-27912-14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.0-preview2.19525.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.9 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET runtimes or SDKs: https://aka.ms/dotnet-download
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (3 by maintainers)

Top Related StackOverflow Question
@SteveSandersonMS - Beautiful solution that works perfectly. I’m so impressed with your answer, I am planning on asking the people, who are responsible for the doc at https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0#business-logic-validation to modify it to incorporate this approach. I feel it would help a lot of community members.
This is a problem. This adds the message under a field called
CaptainsName.Firstname, which is absolutely wrong since none of the properties have that name (with a dot in the middle - it’s not even a valid C# property name). The code should look like the following:… or:
The field identifier is an
objectInstance, propertyNamepair. This is how it gets matched up with an input that reads the same property from the same object instance.