-
-
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.FieldName
just 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: