Blazored / FluentValidation

A library for using FluentValidation with Blazor
https://blazored.github.io/FluentValidation/
MIT License
587 stars 84 forks source link

Is it possible to validate child components? #8

Closed StefH closed 4 years ago

StefH commented 5 years ago

I've this Claim submit razor page:

<EditForm Model="@Model" OnValidSubmit="@Submit">
    <FluentValidationValidator />

    <div class="row">
        <AddressPartial Model="@Model" />
    </div>
</EditForm>

And this Address child page:

@inject ISharedResources Localizer

<LabelText For="@(() => Model.Address.Street)" />
<div class="input-group mb-3">
    <InputText @bind-Value="@Model.Address.Street" />
    <ValidationMessage For="@(() => Model.Address.Street)" />
</div>

@code {
    [Parameter]
    public ClaimBase Model { get; set; }
}

And when I clear the street (which should result in an required error message), this does not work and I get this exception in the browser:

[2019-09-26T18:09:51.529Z] Error: System.InvalidCastException: Unable to cast object of type 'Models.Address' to type 'Models.Claim'.
   at FluentValidation.ValidationContext.ToGeneric[T]() in /home/jskinner/code/FluentValidation/src/FluentValidation/ValidationContext.cs:line 211
   at FluentValidation.AbstractValidator`1.FluentValidation.IValidator.ValidateAsync(ValidationContext context, CancellationToken cancellation) in /home/jskinner/code/FluentValidation/src/FluentValidation/AbstractValidator.cs:line 74
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier, IServiceProvider serviceProvider, IValidator validator) in C:\Users\Web.Blazor\Validation\EditContextFluentValidationExtensions.cs:line 64

Is this supposed to work, or should the below code be changed into:

 validator = GetValidatorForModel(serviceProvider, fieldIdentifier.Model); // fieldIdentifier.Model = the Address and editContext.Model is the Claim
private static async void ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier, IServiceProvider serviceProvider, IValidator validator = null)
        {
            var properties = new[] { fieldIdentifier.FieldName };
            var context = new ValidationContext(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties));

            if (validator == null)
            {
                validator = GetValidatorForModel(serviceProvider, editContext.Model);
            }

            var validationResults = await validator.ValidateAsync(context);

            messages.Clear(fieldIdentifier);
            messages.Add(fieldIdentifier, validationResults.Errors.Select(error => error.ErrorMessage));

            editContext.NotifyValidationStateChanged();
        }
chrissainty commented 5 years ago

I would need to see more of your project code as it looks like you may have an issue in your setup somewhere.

StefH commented 5 years ago

When using the DataAnnotationsValidator it works, fine, when using the FluentValidationValidator, I get error. I think the fix is: validator = GetValidatorForModel(serviceProvider, fieldIdentifier.Model);

See this project for testing. BlazoredDemoAppValidation.zip

chrissainty commented 5 years ago

We're using this control at work and we validate many nested models without any issues. I'm not saying what you're suggesting is wrong but I'm just trying to understand why you have an issue and we don't. Thanks for providing a repro, I'll take a look at this over the weekend and get back to you.

StefH commented 5 years ago

@chrissainty Did you have time to analyze this question?

chrissainty commented 5 years ago

@StefH It's been really weird, we are validating nested models and not seeing any problems. But as you've pointed out in your earlier comment, the code for validating a field doesn't make sense as it will load the wrong validator.

I'm making the change to the code as per your suggestion.

StefH commented 5 years ago

Ok. Thanks for investigating and fixing.

chrissainty commented 5 years ago

This is now all merged and a new version has been pushed to NuGet

NatanBagrov commented 4 years ago

Hi, I'm also getting some weird issues. Here's a code sample:

<EditForm Model="@GroupDto" OnValidSubmit="@UpdateGroup" OnInvalidSubmit="@OnInvalidSubmit">
<FluentValidationValidator/>
...
<div class="form-group">
    <label class="control-label">Name</label>
    <InputText @bind-Value="@GroupDto.InnerGroup.Name" class="form-control"/>
    <ValidationMessage For="@(() => GroupDto.InnerGroup.Name)"/>
</div>

The issues:

  1. Even though the model is GroupDto, the validator tries to instantiate an additional validator Group (a validator for class Group) - I guess it is because an entity Group is composed in GroupDto (member InnerGroup). If such validator does not exist - an exception is thrown.
  2. Even if such validator do exist, <ValidationMessage For="@(() => GroupDto.InnerGroup.Name)"/> shows no validation error upon invalid inputs.
chrissainty commented 4 years ago

Hi @NatanBagrov,

Could you provide a complete repro of the issue as it's really hard to diagnose issues with only a part of the code.

NatanBagrov commented 4 years ago

Hi @NatanBagrov,

Could you provide a complete repro of the issue as it's really hard to diagnose issues with only a part of the code.

Sure, no problem. I'm currently on vacation, so please allow me a few days :)

NatanBagrov commented 4 years ago

Hi @NatanBagrov,

Could you provide a complete repro of the issue as it's really hard to diagnose issues with only a part of the code.

@chrissainty Please find here a repro.Person has an additional member: Location. Upon save we get the following exception:

Unhandled exception in circuit 'vYrAToiBRqW8wXN4Eiq2zwk6cf_6pYrY1OmWh4jm17c'.
System.TypeLoadException: Unable to locate a validator of type FluentValidation.IValidator`1[[SharedModel.Location, SharedModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] or
 FluentValidation.AbstractValidator`1[[SharedModel.Location, SharedModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.GetValidatorForModel(IServiceProvider serviceProvider, Object model) in C:\Users\Natan\RiderProjects\FluentValidation\
src\Blazored.FluentValidation\EditContextFluentValidationExtensions.cs:line 101
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier, IServiceProvi
der serviceProvider, IValidator validator) in C:\Users\Natan\RiderProjects\FluentValidation\src\Blazored.FluentValidation\EditContextFluentValidationExtensions.cs:line 61
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<.cctor>b__23_0(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(WorkItem item)

FluentValidation_PersonLocation.zip

I think the main question we should ask is: when we validate - do we use the validator that suits to Model from <EditForm...> or do we want to look for nested validators (as in the current solution)?

chrissainty commented 4 years ago

Thanks for providing this @NatanBagrov. I've had a bit of a look into things today but I want to check a bit more tomorrow.

NatanBagrov commented 4 years ago

Hi @chrissainty , any updates?

chrissainty commented 4 years ago

@NatanBagrov My apologies, I've had a run-through of your application and I can see the error you're referring to. I've just not had enough time to think through the best solution.

vincentstack commented 3 years ago

HI, has there been a solution to this issue?

chrissainty commented 3 years ago

@vincentstack this was addressed in #18. Are you still experiencing an issue? If so, can you open a new issue with a minimal repro.

vincentstack commented 3 years ago

Yes #18. Thank you.

Really appreciate all you do for the Blazor community.

I really believe that Blazor has enormous potential.

Vince

On Sat, May 1, 2021 at 10:41 AM Chris Sainty @.***> wrote:

@vincentstack https://github.com/vincentstack this was addressed in #18 https://github.com/Blazored/FluentValidation/pull/18. Are you still experiencing an issue? If so, can you open a new issue with a minimal repro.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Blazored/FluentValidation/issues/8#issuecomment-830572786, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIYIXZRKCQPIKLYWFBIKCUTTLOWCXANCNFSM4I25LTDA .

-- Vincent Stack Instructor College of the North Atlantic Qatar Mobile: 5532 2328

rblanca commented 1 year ago

same here...same issue .Net 7

fdbva commented 12 months ago

I have a very similar problem.

My class child was defined as: public ICollection<ExampleEntity> ExampleItems { get; set; } = new HashSet<ExampleEntity>();

It was giving this error:

System.InvalidOperationException: Could not find indexer on object of type System.Collections.Generic.HashSet`1[[SafeTest.Domain.Model.Models.TestEntity, SafeTest.Domain.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
   at Blazored.FluentValidation.EditContextFluentValidationExtensions.ToFieldIdentifier(EditContext& editContext, String& propertyPath)
...

If I change to the following example, it works: public List<ExampleEntity> ExampleItems { get; set; } = new List<ExampleEntity>();

I don't know if it matters, but it's worth mentioning, that the collection had an Enabled and the subcomponents were gated inside either an @if(exampleItem.Enabled) or @foreach (var exampleItem in Example.ExampleItems .Where(x => x.Enabled)).

So I could have a button that toggled the subcomponents that were enabled.