-
-
Save SteveSandersonMS/090145d7511c5190f62a409752c60d00 to your computer and use it in GitHub Desktop.
| public class Customer | |
| { | |
| public string FirstName { get; set; } | |
| public string LastName { get; set; } | |
| public Address Address { get; } = new Address(); | |
| public List<PaymentMethod> PaymentMethods { get; } = new List<PaymentMethod>(); | |
| } | |
| public class Address | |
| { | |
| public string Line1 { get; set; } | |
| public string City { get; set; } | |
| public string Postcode { get; set; } | |
| } | |
| public class PaymentMethod | |
| { | |
| public enum Type { CreditCard, HonourSystem } | |
| public Type MethodType { get; set; } | |
| public string CardNumber { get; set; } | |
| } |
| public class CustomerValidator : AbstractValidator<Customer> | |
| { | |
| public CustomerValidator() | |
| { | |
| RuleFor(customer => customer.FirstName).NotEmpty().MaximumLength(50); | |
| RuleFor(customer => customer.LastName).NotEmpty().MaximumLength(50); | |
| RuleFor(customer => customer.Address).SetValidator(new AddressValidator()); | |
| RuleFor(customer => customer.PaymentMethods).NotEmpty().WithMessage("You have to define at least one payment method"); | |
| RuleForEach(customer => customer.PaymentMethods).SetValidator(new PaymentMethodValidator()); | |
| } | |
| } | |
| public class AddressValidator : AbstractValidator<Address> | |
| { | |
| public AddressValidator() | |
| { | |
| RuleFor(address => address.Line1).NotEmpty(); | |
| RuleFor(address => address.City).NotEmpty(); | |
| RuleFor(address => address.Postcode).NotEmpty().MaximumLength(10); | |
| } | |
| } | |
| public class PaymentMethodValidator : AbstractValidator<PaymentMethod> | |
| { | |
| public PaymentMethodValidator() | |
| { | |
| RuleFor(card => card.CardNumber) | |
| .NotEmpty().CreditCard() | |
| .When(method => method.MethodType == PaymentMethod.Type.CreditCard); | |
| } | |
| } |
| using FluentValidation; | |
| using Microsoft.AspNetCore.Components; | |
| using Microsoft.AspNetCore.Components.Forms; | |
| using System; | |
| namespace CustomValidationSample | |
| { | |
| public class FluentValidator<TValidator> : ComponentBase where TValidator: IValidator, new() | |
| { | |
| private readonly static char[] separators = new[] { '.', '[' }; | |
| private TValidator validator; | |
| [CascadingParameter] private EditContext EditContext { get; set; } | |
| protected override void OnInitialized() | |
| { | |
| validator = new TValidator(); | |
| var messages = new ValidationMessageStore(EditContext); | |
| // Revalidate when any field changes, or if the entire form requests validation | |
| // (e.g., on submit) | |
| EditContext.OnFieldChanged += (sender, eventArgs) | |
| => ValidateModel((EditContext)sender, messages); | |
| EditContext.OnValidationRequested += (sender, eventArgs) | |
| => ValidateModel((EditContext)sender, messages); | |
| } | |
| private void ValidateModel(EditContext editContext, ValidationMessageStore messages) | |
| { | |
| var validationResult = validator.Validate(editContext.Model); | |
| messages.Clear(); | |
| foreach (var error in validationResult.Errors) | |
| { | |
| var fieldIdentifier = ToFieldIdentifier(editContext, error.PropertyName); | |
| messages.Add(fieldIdentifier, error.ErrorMessage); | |
| } | |
| editContext.NotifyValidationStateChanged(); | |
| } | |
| private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string propertyPath) | |
| { | |
| // This method parses property paths like 'SomeProp.MyCollection[123].ChildProp' | |
| // and returns a FieldIdentifier which is an (instance, propName) pair. For example, | |
| // it would return the pair (SomeProp.MyCollection[123], "ChildProp"). It traverses | |
| // as far into the propertyPath as it can go until it finds any null instance. | |
| var obj = editContext.Model; | |
| while (true) | |
| { | |
| var nextTokenEnd = propertyPath.IndexOfAny(separators); | |
| if (nextTokenEnd < 0) | |
| { | |
| return new FieldIdentifier(obj, propertyPath); | |
| } | |
| var nextToken = propertyPath.Substring(0, nextTokenEnd); | |
| propertyPath = propertyPath.Substring(nextTokenEnd + 1); | |
| object newObj; | |
| if (nextToken.EndsWith("]")) | |
| { | |
| // It's an indexer | |
| // This code assumes C# conventions (one indexer named Item with one param) | |
| nextToken = nextToken.Substring(0, nextToken.Length - 1); | |
| var prop = obj.GetType().GetProperty("Item"); | |
| var indexerType = prop.GetIndexParameters()[0].ParameterType; | |
| var indexerValue = Convert.ChangeType(nextToken, indexerType); | |
| newObj = prop.GetValue(obj, new object[] { indexerValue }); | |
| } | |
| else | |
| { | |
| // It's a regular property | |
| var prop = obj.GetType().GetProperty(nextToken); | |
| if (prop == null) | |
| { | |
| throw new InvalidOperationException($"Could not find property named {nextToken} on object of type {obj.GetType().FullName}."); | |
| } | |
| newObj = prop.GetValue(obj); | |
| } | |
| if (newObj == null) | |
| { | |
| // This is as far as we can go | |
| return new FieldIdentifier(obj, nextToken); | |
| } | |
| obj = newObj; | |
| } | |
| } | |
| } | |
| } |
| <EditForm Model="customer" OnValidSubmit="SaveCustomer"> | |
| <FluentValidator TValidator="CustomerValidator" /> | |
| <h3>Your name</h3> | |
| <InputText placeholder="First name" @bind-Value="customer.FirstName" /> | |
| <InputText placeholder="Last name" @bind-Value="customer.LastName" /> | |
| <ValidationMessage For="@(() => customer.FirstName)" /> | |
| <ValidationMessage For="@(() => customer.LastName)" /> | |
| <h3>Your address</h3> | |
| <div> | |
| <InputText placeholder="Line 1" @bind-Value="customer.Address.Line1" /> | |
| <ValidationMessage For="@(() => customer.Address.Line1)" /> | |
| </div> | |
| <div> | |
| <InputText placeholder="City" @bind-Value="customer.Address.City" /> | |
| <ValidationMessage For="@(() => customer.Address.City)" /> | |
| </div> | |
| <div> | |
| <InputText placeholder="Postcode" @bind-Value="customer.Address.Postcode" /> | |
| <ValidationMessage For="@(() => customer.Address.Postcode)" /> | |
| </div> | |
| <h3> | |
| Payment methods | |
| [<a href @onclick="AddPaymentMethod">Add new</a>] | |
| </h3> | |
| <ValidationMessage For="@(() => customer.PaymentMethods)" /> | |
| @foreach (var paymentMethod in customer.PaymentMethods) | |
| { | |
| <p> | |
| <InputSelect @bind-Value="paymentMethod.MethodType"> | |
| <option value="@PaymentMethod.Type.CreditCard">Credit card</option> | |
| <option value="@PaymentMethod.Type.HonourSystem">Honour system</option> | |
| </InputSelect> | |
| @if (paymentMethod.MethodType == PaymentMethod.Type.CreditCard) | |
| { | |
| <InputText placeholder="Card number" @bind-Value="paymentMethod.CardNumber" /> | |
| } | |
| else if (paymentMethod.MethodType == PaymentMethod.Type.HonourSystem) | |
| { | |
| <span>Sure, we trust you to pay us somehow eventually</span> | |
| } | |
| <button type="button" @onclick="@(() => customer.PaymentMethods.Remove(paymentMethod))">Remove</button> | |
| <ValidationMessage For="@(() => paymentMethod.CardNumber)" /> | |
| </p> | |
| } | |
| <p><button type="submit">Submit</button></p> | |
| </EditForm> | |
| @code { | |
| private Customer customer = new Customer(); | |
| void AddPaymentMethod() | |
| { | |
| customer.PaymentMethods.Add(new PaymentMethod()); | |
| } | |
| void SaveCustomer() | |
| { | |
| Console.WriteLine("TODO: Actually do something with the valid data"); | |
| } | |
| } |
Hi Steve,
I implementing this FluentValidator using the above codes. but i am getting this error.
how can we resolve this error? Could you please check this and update the details to resolve the problem?
Regards,
Rahulplease refer on the description here.. https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html
you can replace that with:var context = new ValidationContext<object>(editContext.Model); var validationResult = validator.Validate(context);
thank you for the info... it works
Adding validation just for a field:
protected override void OnInitialized()
{
validator = new TValidator();
var messages = new ValidationMessageStore(EditContext!);
// Revalidate when any field changes, or if the entire form requests validation
// (e.g., on submit)
EditContext!.OnFieldChanged += (sender, eventArgs)
=> ValidateField((EditContext)sender!, messages, eventArgs.FieldIdentifier); //<--- THIS
EditContext.OnValidationRequested += (sender, eventArgs)
=> ValidateModel((EditContext)sender!, messages);
}
private void ValidateField(EditContext editContext, ValidationMessageStore messages, FieldIdentifier fieldIdentifier) //<--- HERE
{
var properties = new[] { fieldIdentifier.FieldName };
var context = new ValidationContext<object>(editContext.Model, new PropertyChain(), new MemberNameValidatorSelector(properties));
var validationResult = validator!.Validate(context);
messages.Clear(fieldIdentifier);
messages.Add(fieldIdentifier, validationResult.Errors.Select(error => error.ErrorMessage));
editContext.NotifyValidationStateChanged();
}Any solution for OnFieldChange with field of item in list ?
With code from @ctrl-alt-d we can only valid field in object. fieldIdentifier.FieldName just is field name. I need field path
Any solution for OnFieldChange with field of item in list ?
With code from @ctrl-alt-d we can only valid field in object.
fieldIdentifier.FieldNamejust is field name. I need field path
What do you mean by field path here?

please refer on the description here.. https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html
you can replace that with: