Blazored / FluentValidation

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

Nested validator #65

Closed kedzior-io closed 3 years ago

kedzior-io commented 3 years ago

I have this form where I'm building up the list of addresses:

image

Here is the example: https://github.com/kedzior-io/blazor-webassmebly-aspnetcore-hosted-fluent-validation/blob/master/BlazorFluentValidation/Client/Pages/Index.razor

<EditForm Model="@Person">
    <FluentValidationValidator />

    <div class="form-group">
        <label>First Name: </label>
        <InputText @bind-Value="@Person.FirstName" class="form-control" />
        <ValidationMessage For="@(() => Person.FirstName)" />
    </div>

    <div class="form-group">
        <label>Last Name: </label>
        <InputText @bind-Value="@Person.LastName" class="form-control" />
        <ValidationMessage For="@(() => Person.LastName)" />
    </div>

    <div class="form-group">
        <label>Address List: </label>
        @foreach (var address in Person.Adresses)
        {
            <p>- @address.City, @address.Country</p>
        }
    </div>

    <hr />

    <p>Add Address</p>
    <FluentValidationValidator Validator="AddressValidator" />
    <div class="form-group">
        <label>City: </label>
        <InputText @bind-Value="@NewAddress.City" class="form-control" />
        <ValidationMessage For="@(() => NewAddress.City)" />
    </div>
    <div class="form-group">
        <label>Country: </label>
        <InputText @bind-Value="@NewAddress.Country" class="form-control" />
        <ValidationMessage For="@(() => NewAddress.Country)" />
    </div>

    <button @onclick="AddAddress">Add Address</button>

    <hr />

    <button type="submit">Save</button>

    @if (isValid)
    {
        <div class="alert alert-success" role="alert">
            Oh wow! I'm valid!
        </div>
    }
</EditForm>

@code {
    Person Person { get; set; } = new Person();
    Address NewAddress { get; set; } = new Address();
    AddressValidator AddressValidator = new AddressValidator();

    bool isValid { get; set; } = false;

    public void AddAddress()
    {
        var result = AddressValidator.Validate(NewAddress);

        if (!result.IsValid)
        {
            isValid = false;
            return;
        }

        Person.Adresses.Add(NewAddress);
        NewAddress = new Address();
        isValid = true;
    }
}

When I hit "Add Address" validation fires for first name and last name and completely ignores city and country.

When I remove:

<FluentValidationValidator Validator="AddressValidator" />

and instantiate validator manually:

    public void AddAddress()
    {
        var addressValidator = new AddressValidator();
        var result = addressValidator.Validate(NewAddress);
     //  ...
    }

it catches errors on address instance but it doesn't show it on the form below corresponding inputs.

Any idea why?

The only way I managed to get it working is splitting it into two forms (which is not ideal): https://github.com/kedzior-io/blazor-webassmebly-aspnetcore-hosted-fluent-validation/blob/master/BlazorFluentValidation/Client/Pages/Working.razor

chrissainty commented 3 years ago

First of all, you don't need two validators in your EditForm. You also can't have two models. The validator validates the model provided to the EditForm. This will be why you're not seeing validation happening on the address, the validator knows nothing about it.

I would suggest checking out the example below which shows configuring a model with a nested validator. I would also suggest checking out the fluent validation documentation on the subject.

https://github.com/chrissainty/blazor-in-action/blob/705387375ce674fdb47d8d7afb4f630be0be2fb1/chapter-06/BlazingTrails/BlazingTrails.Shared/Features/ManageTrails/Shared/TrailDto.cs#L32