-
-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix inconsistent field-level validation (Fixes #204) #205
Fix inconsistent field-level validation (Fixes #204) #205
Conversation
1eb55dc
to
d8d4443
Compare
9b7fd1d
to
2faff2f
Compare
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 Is that what you were expecting to happen? |
7bfb8eb
to
2faff2f
Compare
@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. |
return BuildPropertyPath(currentNode, fieldIdentifier); | ||
} | ||
|
||
var nonPrimitiveProperties = currentModelObject?.GetType() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had an issue with this bit of code, in a model it kept adding DateTime types on the NodeStack in an endless loop, eventually ending in out of memory. I think additional type guards are needed here for types such as DateTime
. This change to check for additional simple types fixed the issue with DateTime not being a primitive.
jafin@2de01ab
@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 |
Has anyone tested this PR with more than 5 fields and a non-trivial model? We have hundreds of fields in our model with lists of more complex models. Because our form is user-configurable, we have effectively a tree of those complex models. The performance of We use this package since 2021 and had never issues with the default Concerning the original issue from #204, I think there should be a better way to select the validator of the root model, if needed. It should at least be configurable, as for our project, the previous selection strategy worked better. It seems like a difficult problem, though, considering #235, #76, and #104. |
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