-
-
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"); | |
} | |
} |
@nssidhu sorry it's taken so long to reply. It's as simple as adding a property to the Validator
class decorating it with the [Inject]
attribute. For example: [Inject] private IServiceProvider Services { get; set; }
How i can fix this error
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,
Rahul
please 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);
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?
@stevenbey can you show how you were able to inject IserviceProvider.
I have similar need where i want to inject/pass in Httpclient from blazor page for server side validation into my CustomerValidator