Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Blazor + FluentValidation example
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");
}
}
@stevenbey

This comment has been minimized.

Copy link

@stevenbey stevenbey commented Mar 16, 2020

This is a great solution; however some of my validators have dependencies, so I had to remove the new() constraint, inject an IServiceProvider and update OnInitialized (line 17) to: validator = Services.GetService<TValidator>() ?? Activator.CreateInstance<TValidator>();

Many thanks

@nssidhu

This comment has been minimized.

Copy link

@nssidhu nssidhu commented Apr 24, 2020

Validation does not happen on the tab out of field. only works at the submit level

@nssidhu

This comment has been minimized.

Copy link

@nssidhu nssidhu commented Jun 19, 2020

How can one pass in HttpClient to the CustomerValidator ?

@nssidhu

This comment has been minimized.

Copy link

@nssidhu nssidhu commented Jun 21, 2020

@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

@stevenbey

This comment has been minimized.

Copy link

@stevenbey stevenbey commented Jul 16, 2020

@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; }

@icedenis

This comment has been minimized.

Copy link

@icedenis icedenis commented Jul 31, 2020

How i can fix this error

image

@Rahul-Narayanasamy

This comment has been minimized.

Copy link

@Rahul-Narayanasamy Rahul-Narayanasamy commented Aug 3, 2020

Hi Steve,

I implementing this FluentValidator using the above codes. but i am getting this error.
image

how can we resolve this error? Could you please check this and update the details to resolve the problem?

Regards,
Rahul

@natanaeladit

This comment has been minimized.

Copy link

@natanaeladit natanaeladit commented Aug 5, 2020

Hi Steve,

I implementing this FluentValidator using the above codes. but i am getting this error.
image

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);
@Rahul-Narayanasamy

This comment has been minimized.

Copy link

@Rahul-Narayanasamy Rahul-Narayanasamy commented Aug 8, 2020

Hi Steve,
I implementing this FluentValidator using the above codes. but i am getting this error.
image
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);

thank you for the info... it works

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.