Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation on childcollection fails if using the Validator property #188

Closed
jstrandelid opened this issue May 3, 2023 · 7 comments · Fixed by #205
Closed

Validation on childcollection fails if using the Validator property #188

jstrandelid opened this issue May 3, 2023 · 7 comments · Fixed by #205
Labels
Bug Something isn't working

Comments

@jstrandelid
Copy link

Setup
Hosted Blazor WebAssembly (default template in VS)
FluentValidation 11.5.2
Blazored.FluentValidation 2.1.0

The code has three ways validate a personcollection (2persons in each collection, one collection per example)
"Assembly validation" is using separate validator classes and detected by assembly scanning.
"Validator property" is using separate validator classes, but instead of using scanning the Validator property is given an instance of the PersonCollectionValidator.
"Inline validator" is using an instance of inline validators, where the IValidator instance is given to the Validator property of then FluentValidationValidator.

Expected
For all examples.
If a modified input value fails validation a validation message should appear below the input.
When clicking the submit button, all fields that fails validation a validation message should appear below each input.

Actual
For "Assembly validation" all works as expected.
For the other two examples that are using the Validation property validation works when clicking on the submit button, but an exception is thrown when the input lost focus and an error message appears in the browser console stating "System.InvalidOperationException: Cannot validate instances of type 'PersonClass'. This validator can only validate instances of type 'PersonCollection'."
image

Code
(index.razor is modified)


@page "/"
@using System.ComponentModel.DataAnnotations;
@using Blazored.FluentValidation;
@using FluentValidation;
@using Microsoft.Extensions.Localization;
@using System.Diagnostics;

<PageTitle>Index</PageTitle>

<h1>Assembly validation</h1>
<EditForm Model=@pc1>
    <FluentValidationValidator  />
    @foreach(var person in pc1.Persons) {
        <div class="form-group">
            <label for="Name">Name</label>
            <InputText @bind-Value="@person.Name"/>
            <ValidationMessage For=@( () => person.Name) />
        </div>
        <div class="form-group">
            <label for="Age">Age</label>
            <InputNumber @bind-Value=person.Age class="form-control" id="Age" />
            <ValidationMessage For=@( () => person.Age ) />
        </div>
    }


    <input type="submit" class="btn btn-primary" value="Save" />
</EditForm>
<br/>
<h1>Validator property</h1>
<EditForm Model=@pc2>
    <FluentValidationValidator Validator="@validator" />
    @foreach (var person in pc2.Persons)
    {
        <div class="form-group">
            <label for="Name">Name</label>
            <InputText @bind-Value="@person.Name"/>
            <ValidationMessage For=@( () => person.Name) />
        </div>
        <div class="form-group">
            <label for="Age">Age</label>
            <InputNumber @bind-Value=person.Age class="form-control" id="Age" />
            <ValidationMessage For=@( () => person.Age ) />
        </div>
    }


    <input type="submit" class="btn btn-primary" value="Save" />
</EditForm>
<br/>
<h1>Inline validator</h1>
<EditForm Model=@pc3>
    <FluentValidationValidator Validator="@inlineValidator" />
    @foreach (var person in pc3.Persons)
    {
        <div class="form-group">
            <label for="Name">Name</label>
            <InputText @bind-Value="@person.Name"/>
            <ValidationMessage For=@( () => person.Name) />
        </div>
        <div class="form-group">
            <label for="Age">Age</label>
            <InputNumber @bind-Value=person.Age class="form-control" id="Age" />
            <ValidationMessage For=@( () => person.Age ) />
        </div>
    }


    <input type="submit" class="btn btn-primary" value="Save" />
</EditForm>

@code {
    PersonCollection pc1 = new PersonCollection();
    PersonCollection pc2 = new PersonCollection();
    PersonCollection pc3 = new PersonCollection();

    PersonClass Person = new PersonClass();
    IValidator<PersonCollection> validator = new PersonCollectionValidator();
    IValidator inlineValidator = CreateInlineValidator();

    private static IValidator CreateInlineValidator()
    {
        var collectionVal = new InlineValidator<PersonCollection>();
        var personVal = new InlineValidator<PersonClass>();
        personVal.RuleFor(x => x.Name).NotEmpty();
        collectionVal.RuleForEach(x => x.Persons).SetValidator(personVal);

        return collectionVal;
    }

    public class PersonCollection
    {
        public IEnumerable<PersonClass> Persons { get; set; } = new List<PersonClass>
        {
            new PersonClass { Age=1, Name="A"},
            new PersonClass { Age=2, Name="B"}
        };

        public IValidator GetValidator()
        {
            var inlineVal = new PersonCollectionValidator();
            inlineVal.RuleForEach(x => x.Persons).SetValidator(new PersonClassValidator());
            return inlineVal;
        }
    }

    public class PersonClass
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class PersonCollectionValidator : AbstractValidator<PersonCollection>
    {
        public PersonCollectionValidator()
        {
            RuleForEach(x => x.Persons).SetValidator(new PersonClassValidator());
        }
    }

    public class PersonClassValidator : AbstractValidator<PersonClass>
    {
        public PersonClassValidator()
        {
            RuleFor(x => x.Name).NotEmpty();
        }
    }
}
@jstrandelid jstrandelid added Bug Something isn't working Triage Issue needs to be triaged labels May 3, 2023
@jstrandelid
Copy link
Author

After some examination I notice that the ValidateField function that is triggered when a field has changed is receiving the "root" validator ("PersonCollectionValidator") and not the validator set in the "RuleForEach" for Persons, but the model given in the eventargs is of the type "PersonClass" . Hence the validator of "PersonCollection" type can not validate a PersonClass object.

The ValidateModel function, that is triggered by a submit in the form, validates the "root model"(PersonCollection) and the validator is the "root validator" (PersonCollectionValidator) and therefor it works.

@jstrandelid
Copy link
Author

I think this issue regards nested validators.

@matthetherington
Copy link

matthetherington commented Dec 6, 2023

Hi, this sounds like it would be fixed by #205 - please could you weigh in

@jstrandelid
Copy link
Author

Hi Matt,
From what I can understand of your description in #205 (and #204) it sounds like a good solution that would work.
Unfortunately, my knowledge of FluentValidation and Blazored.FluentValidation is lacking a bit, so I can't weight in on if it's the best solution or understand the impact of the solution.
If you'd like i could test some different scenarios with the commited code.

@matthetherington
Copy link

That'd be very helpful, thanks!

@alperenbelgic
Copy link

alperenbelgic commented Feb 8, 2024

I know it looks horrible, but when I place try & empty catch around below. validations for nested object just work (don't ask me how) and I don't get the breaking errors. I got the source code & project into my solution. I'll observe for a while. I just wanted to share, and also would love to know if it works in other use cases as well.

https://github.com/Blazored/FluentValidation/blob/ef26fc1b9c34e2e4afe8d16d61dbfc11e431b204/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs#L84C1-L89C56

@jstrandelid
Copy link
Author

I "finally" had time to test this (sorry about the long delay).
I my first test was with the given example above and it does not work quite as I expected. (i might have done something wrong getting the code)
Could you please confirm my finding.

The errors in the console are gone, but if I:
1, enter a name (in any of the examples)
2. leave the input
3. enter the same input and erase the name
4. leave the input.

I would expect that "'Name' must not be empty" should be shown, however it's not.
The input has a green border as it is valid.
If I press the associated save button the error message for Name is shown and the input has an "invalid" state.

Expected
After an input is modified and the value is invalid, I would expect the error message to should and the input should show an invalid state (red border).

@jstrandelid jstrandelid closed this as not planned Won't fix, can't repro, duplicate, stale Apr 10, 2024
@jstrandelid jstrandelid reopened this Apr 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants