question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Blazor EditForm validation only partially works when using a 'Validator component' (within a 'business logic validation' function)

See original GitHub issue

Describe 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.

blazorValidationIssue8Mar21

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>,
        &copy;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:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
RichardBrcommented, Mar 8, 2021

@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.

0reactions
SteveSandersonMScommented, Mar 8, 2021

errors.Add(“CaptainsName.Firstname”, … … messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);

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:

messageStore.Add(FieldIdentifier.Create(starship.CaptainsName, "Firstname"), "Your message goes here");

… or:

messageStore.Add(FieldIdentifier.Create(() => starship.CaptainsName.Firstname), "Your message goes here");

The field identifier is an objectInstance, propertyName pair. This is how it gets matched up with an input that reads the same property from the same object instance.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Blazor EditForm validation only partially works when using ...
Blazor EditForm validation only partially works when using a 'Validator component' (within a 'business logic validation' function).
Read more >
ASP.NET Core Blazor forms and input components
Learn how to use forms with field validation and built-in input components in Blazor.
Read more >
Blazor WASM Forms Validation - I ❤️ DotNet - ilovedotnet.org
In this article, let's learn about how to validate the data collected using forms in Blazor WASM application.
Read more >
How do you manually trigger the form validation in Blazor?
You have to define and bind the EditContext with EditForm and then call the method editContext. Validate() on button click to manually trigger...
Read more >
Telerik UI Blazor 2.22.0—Form and Validation Components
To enable form validation, you need to add <FormValidation> tag in it and provide a validator—for example the DataAnnotationsValidator that ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found