3

Let's say we have simple Object that contains two of another type

public class Parent
{
     [ValidateComplexType]
     public Child Child1 { get; set; }

     [ValidateComplexType]
     public Child Child2 { get; set; }
}
 
public class Child : IValidatableObject
{
     public String Name { get; set; } = String.Empty
     
     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
     {
         return new ValidationResult("Error", new[] { nameof(Name) })
     }
}

I managed to do nested validation by using ObjectGraphDataAnnotationsValidator as suggested at https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0#nested-models-collection-types-and-complex-types

Now let's say that I don't want Child2 to have the same Name as Child 1, so I need to compare their Name properties and display an error on the Child2 input field. If I do this by adding IValidatableObject to the Parent and in the Validate method return new ValidationResult("Error", new[] { nameof(Child2.Name) }) this doesn't actually set the field as invalid.

I thought about adding a Func<Child, Boolean> to each child and then set it when I Instantiate the Parent object, that looks like child => child == Child2 && Child2.Name == Child1.Name and it works but it is very confusing in my opinion. How to do this properly?

2

2 Answers 2

3

In my humble opinion, you need to use custom validation here to check if Child2 has the same Name as Child1. I did a test in a blazor server application. My model has 2 properties which are Name1 and Name2.

enter image description here

public class ExampleModel
{
    [Required]
    public UserTest userName1 { get; set; }
    [Required]
    public UserTest userName2 { get; set; }
}

public class UserTest {
    [StringLength(10, ErrorMessage = "Name is too long.")]
    public string userName { get; set; }
}


@page "/form-example-1"
@using BlazorAppServer.Model
<h3>FormExample1</h3>

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <CustomValidation @ref="customValidation" />
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.userName1.userName" />
    <InputText id="name" @bind-Value="exampleModel.userName2.userName" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new() { userName1 = new UserTest { userName="asdfgh"}, userName2 = new UserTest { userName="hgfdsa"} };
    private CustomValidation customValidation;

    private void HandleValidSubmit()
    {
        customValidation.ClearErrors();
        var a = exampleModel.userName1.userName;
        var b = exampleModel.userName2.userName;
        var errors = new Dictionary<string, List<string>>();
        if (a == b)
        {
            errors.Add(nameof(exampleModel.userName2.userName), new() { "name2 can't be the same as name1" });
        }
        if (errors.Any())
        {
            customValidation.DisplayErrors(errors);
        }
    }
}
    
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Collections.Generic;

namespace BlazorAppServer
{
    public class CustomValidation : ComponentBase
    {
        private ValidationMessageStore messageStore;

        [CascadingParameter]
        private EditContext CurrentEditContext { get; set; }
        protected override void OnInitialized()
        {
            if (CurrentEditContext == null)
            {
                throw new InvalidOperationException(
                    $"{nameof(CustomValidation)} requires a cascading " +
                    $"parameter of type {nameof(EditContext)}. " +
                    $"For example, you can use {nameof(CustomValidation)} " +
                    $"inside an {nameof(EditForm)}.");
            }

            messageStore = new(CurrentEditContext);

            CurrentEditContext.OnValidationRequested += (s, e) =>
                messageStore.Clear();
            CurrentEditContext.OnFieldChanged += (s, e) =>
                messageStore.Clear(e.FieldIdentifier);
        }

        public void DisplayErrors(Dictionary<string, List<string>> errors)
        {
            foreach (var err in errors)
            {
                messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }

        public void ClearErrors()
        {
            messageStore.Clear();
            CurrentEditContext.NotifyValidationStateChanged();
        }
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I'm sorry but after trying, if you have nested models, this doesnt set the proper field as invalid. In your case you have the exampleModel as Model for the EditForm but in mine i would have exampleModel.NestedModel.Name1
I've updated my code with a nested model and it also worked well, pls check it. I didn't change code in the CustomValidation
You can use <ObjectGraphDataAnnotationsValidator ></ObjectGraphDataAnnotationsValidator> in Microsoft.AspNetCore.Components.Forms. You will not require extra changes also and it works fine with editform
(Commenting on behalf of @Nikolas ) : Unfortunately, @TinyWang 's code does not seem to work. I've created a project located here [source][1] with my own example with nested object and with Tiny Wang's code at the url (/form-example-1) [1]: github.com/nifragos/blazor-nested-validation-problem
1

you can use this

https://code-maze.com/complex-model-validation-in-blazor/

Microsoft.AspNetCore.Components.DataAnnotations.Validation

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.