Fluent Validation does not work as expected in child components

1.4k views Asked by At

I'm trying to set up fluent validation for my application. Validation works fine for parent component. It shows messages as leave a field. However, it does not work in same way with child components. Validation messages do not appear.

Validation messages do not appear in child component.

It works only if I execute EditContext.Validate method.

Validation works when I execute EditContext.Validate method.

Any ideas how to fix this? Thank you.

Models:

namespace Application.Models
{
    public class Company
    {
        ...
        public string Name { get; set; }

        public int headquartersId { get; set; }
        public Address Headquarters { get; set; }
        ...
    }
    public class Address
    {
        ...
        public string Street { get; set; }
        ...
    }
}

Validators:

using Application.Models
using FluentValidation

namespace Application.Validators
{
   public class CompanyValidator: AbstractValidator<Company>
   {
      public CompanyValidator()
      {
         RuleFor(fields => fields.Name)
         .MinimumLength(5)
         .WithMessage("---");

         RuleFor(field => field.Headquarters).SetValidator(new AddressValidator());
      }
   }
   public class AddressValidator: AbstractValidator<Address>
   {
      public AddressValidator()
      {
          RuleFor(fields => fields.Street)
          .MinimumLength(5)
          .WithMessage("---");
      }
   }
}

Parent Component:

@using Application.Models.Cards.Customers
@using Application.Models
@using FluentValidation

<EditForm EditContext="editContext" OnSubmit="Submit">

    <FluentValidationValidator />

    <div class="form-group">
        <label for="fname">Business Entity Name</label>
        <InputText name="fname" @bind-Value="Customer.Name" class="form-control"></InputText>
        <ValidationMessage For="@(() => Customer.Name)" />
    </div>
    <div class="form-group">
        <label for="fname">Business Entity Code</label>
        <InputText name="fname" @bind-Value="Customer.Code" class="form-control"></InputText>
        <ValidationMessage For="@(() => Customer.Code)"></ValidationMessage>
    </div>
    <div class="form-group">
        <label for="fname">Business Entity VAT</label>
        <InputText name="fname" @bind-Value="Customer.VAT" class="form-control"></InputText>
        <ValidationMessage For="@(() => Customer.VAT)"></ValidationMessage>
    </div>
    **<AddressComponent Address="Customer.Headquarters" SetCurrentAddress="GetAddress"/>**

    <button type="submit" class="btn btn-primary">Save</button>

</EditForm>

@code {
    [Parameter]
    public Company Customer { get; set; }

    #nullable enable
    private EditContext? editContext;

    protected override Task OnInitializedAsync()
    {
        editContext = new EditContext(Customer);
        return base.OnInitializedAsync();
    }

    private void GetAddress(Address address)
    {
        Customer.Headquarters = address;
    }

    private void Submit()
    {
        editContext?.Validate();
    }
}

Address Component:

@using Application.Models

<div class="row">
    <div class="form-group col-3">
        <label for="house">House</label>
        <input type="number" name="house" @bind-Value="Address.House" @bind-Value:event="oninput"  class="form-control" />
        <ValidationMessage For="@(() => Address.House)" />
    </div>
    <div class="form-group col-9">
        <label for="street">Street</label>
        <input type="text" name="street" @bind="Address.Street" @bind:event ="oninput" @onkeyup="OnAddressChange" class="form-control" />
        <ValidationMessage For="@(() => Address.Street)" />
    </div>
</div>
<div class="row">
    <div class="form-group col-3">
        <label for="postCode">Post Code</label>
        <input type="text" name="postCode" @bind="Address.PostCode" @bind:event="oninput" @onkeyup="OnAddressChange" class="form-control" />
        <ValidationMessage For="@(() => Address.PostCode)" />
    </div>
    <div class="form-group col-9">
        <label for="county">County</label>
        <input type="text" name="county" @bind="@Address.County" @bind:event="oninput" @onkeyup="OnAddressChange" class="form-control" />
        <ValidationMessage For="@(() => Address.County)" />
    </div>
</div>
<div class="row">
    <div class="form-group col-12">
        <label for="country">Country</label>
        <input type="text" name="country" @bind="Address.Country" @bind:event="oninput" @onkeyup="OnAddressChange" class="form-control" />
        <ValidationMessage For="@(() => Address.Country)" />
    </div>
</div>


@code {

    [Parameter]
    public Address Address { get; set; }

    [Parameter]
    public EventCallback<Address> SetCurrentAddress { get; set; }

    private async Task OnAddressChange()
    {
        await SetCurrentAddress.InvokeAsync(Address);
    }
}

1

There are 1 answers

0
Steve Greene On

Not sure if this will solve your problem, but it works for me. I use FluentValidationValidator in a different way:

My editform would look like:

<EditForm Model="@Customer" OnSubmit="Submit">
...

Where "@Customer" is an instance of the model. Then I setup my validator with a reference:

<FluentValidationValidator @ref="fluentValidationValidator" />

And define a variable to handle it:

private FluentValidationValidator fluentValidationValidator;

Then when I want to validate:

var validationResults = fluentValidationValidator.Validate(opts => opts.IncludeAllRuleSets());
if (!validationResults) return;
// model is valid, save, etc.

Chris lays out some of the behind the scenes stuff here: https://chrissainty.com/using-fluentvalidation-for-forms-validation-in-razor-components/