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

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();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With