1

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);
    }
}

Justas
  • 39
  • 1
  • 7

1 Answers1

0

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/

Steve Greene
  • 12,029
  • 1
  • 33
  • 54