Closed matthetherington closed 1 month ago
EDIT: You can ignore this comment. I messed something up when I tried the PR. It's working fine as far as I can tell.
Was asked to provide some feedback in #180, since we ended-up making our own validation handler at work to tackle something similar.
From playing around with it, it does seem to properly validate nested objects' fields, though it's worth pointing-out that it doesn't address the problem discussed in #180 (all RuleSets firing, regardless of options), but it's like I said in that discussion:
FluentValidation's API doesn't expose the necessary functions to fix that (which is why our fix at work validates the entire model on field change and only tracks the new validation errors so we can fake a field-speicific validation).
If anyone's interested in trying to fix that specific issue with this PR, here's the basic example I was using to reproduce it:
public class Person
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public Address Address { get; set; } = new();
}
public class Address
{
public string Street { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string PostalCode { get; set; } = string.Empty;
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator(IValidator<Address> addressValidator)
{
RuleFor(a => a.Address).SetValidator(addressValidator);
RuleFor(p => p.Name).NotEmpty().WithMessage("Name required");
RuleSet("Adult", () =>
{
RuleFor(p => p.Age).GreaterThan(18).WithMessage("Must be an adult");
});
}
}
public class AddressValidator : AbstractValidator<Address>
{
public const string PostalCodePattern = "[a-zA-Z][0-0][a-zA-Z] [0-9][a-zA-Z][0-9]";
public static readonly Regex PostalCodeRegex = new(PostalCodePattern);
public AddressValidator()
{
RuleFor(a => a.Country).NotEmpty().WithMessage("Country required");
RuleFor(a => a.Street).NotEmpty().WithMessage("Street required");
RuleFor(a => a.PostalCode).Matches(PostalCodeRegex).WithMessage("Must be valid postal code");
RuleSet("Canadian", () =>
{
RuleFor(a => a.Country)
.NotEmpty().WithMessage("Country required")
.Equal("Canada").WithMessage("Must be Canadian");
});
}
}
@page "/"
@using Microsoft.AspNetCore.Components.Forms
@using BlazorServerApp.Models
@using Blazored.FluentValidation
<EditForm Model="_person" OnValidSubmit="() => { }">
@* NOTE: The options' rulesets are basically ignored, you can use whatever RuleSet you want, and it'll still run all of them *@
<FluentValidationValidator Options="@(options => options.IncludeRuleSets("default"))"/>
<ValidationSummary/>
<h1>Person</h1>
<div>
<label>Name</label>
<InputText @bind-Value="_person.Name"/>
</div>
<div>
<label>Age</label>
<InputNumber @bind-Value="_person.Age"/>
</div>
<h2>Address</h2>
<div>
<label>Country</label>
<InputText @bind-Value="_person.Address.Country"/>
</div>
<div>
<label>Street</label>
<InputText @bind-Value="_person.Address.Street"/>
</div>
<div>
<label>Postal Code</label>
<InputText @bind-Value="_person.Address.PostalCode"/>
</div>
<button type="submit">
Submit
</button>
</EditForm>
@code {
private readonly Person _person = new();
}
Thanks for that @UniMichael
I tried to recreate the problem you were seeing, but it seems to be working okay when using the modified Blazored.FluentValidation from this PR.
I can enter 17 for age, and UK as the country, and both fields are valid when blurring out.
Submitting the form also does not produce a validation message for those fields, so it does seem to be using only the default rule set as specified in the <FluentValidationValidator>
config and not including Adult / Canadian. Omitting the options produces the same result.
Is that what you were expecting to happen?
And here's the result when I include the validator via:
<FluentValidationValidator Options="@(options => options.IncludeRuleSets("Adult"))"/>
The postcode format check is not ran, so the default ruleset for address is not being ran, and the "must be an adult" message appears, so the Adult ruleset is being ran as per the options provided.
@matthetherington Ok, I think I messed something up when I tried-out the PR. I may have forgotten to actually checkout the branch after I pulled it. I'm running it now and it does seem like the rule problem was fixed.
That's what I get for testing it on a Friday morning without having coffee, I guess. 😅
I'll edit my other comment.
@UniMichael ah brilliant, thanks for taking another look!
@matthetherington Hey, so I've tried the PR and it seems to be working fine. However, I find the partial-validation a bit odd. If you first input some non-allowed value in to the other fields, it displays a few errors. But if you then press the partial-validate, the existing errors are deleted, so that it seems that they are now OK. I would expect the partial-validate to validate only the fields included in rule-set, but leave existing errors etc to be?
@matthetherington I've been using the Blazored.FluentValidation library and find it to be excellent, but I encountered issue #204 a week ago. I've tested the PR, and it seems to resolve the problem with the sub-objects for me.
Do you expect this PR to be merged anytime soon?
Please merge this soon, I have been waiting for this since end of last year when we switched to .net8.
Any timeline on when this will be reviewed/merged? I am also having this problem and this PR seems to resolve it.
I am in need of this fix as well--as I'm blocked using FluentValidation without it.
Any updates on merging this in? Been waiting a VERY long time for this and it's holding up our project. I like to rely on and support 3rd party packages however this has taken far too long to resolve.
apologies, I'll work to get this resolved and merged
This fixes a few issues that are causing inconsistent behaviour between field-level and whole-model validation.
See #204 for more context and a demo project
Summary of changes:
EditContext.Model
is used to locate a validator for field-level validation if one is not providedFieldIdentifier
is converted to a Fluent property path (egCustomer.Address[0].Line1
) by searching theEditContext.Model
instance, and then the validation results at that path are used to determine if the field is valid. The<FluentValidationValidator>
options are supplied to Fluent, but are combined with an additional restriction so that only rules affecting the changed property are ran (as per the defaultMemberNameValidatorSelector
behaviour)<FluentValidationValidator>
that is currently used for whole-model validation is also used when validating fields, this means that RuleSets and other configuration work as expected at the field-level.The above would just mirror the existing whole-model validation for field-level validation, so I think it's a fairly safe change. If a project is using Blazored.FluentValidation then they'll have a validator configured that works for the whole model.
Could I please get your thoughts? I'm looking at this through quite a narrow lense so there may be issues with the proposal that haven't occured to me - it does seem to work okay though.
Resolves #204 Resolves #188 Resolves #180