Skip to content

Commit

Permalink
Add FluentValidation RuleSets and partial validation support (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
GioviQ authored Feb 19, 2021
1 parent cd0e981 commit e88697f
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 38 deletions.
2 changes: 1 addition & 1 deletion samples/BlazorServer/BlazorServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.0.0" />
<PackageReference Include="FluentValidation" Version="9.2.2" />
</ItemGroup>

<ItemGroup>
Expand Down
25 changes: 21 additions & 4 deletions samples/BlazorServer/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
<hr class="mb-5" />

<EditForm Model="@Person" OnValidSubmit="@SubmitValidForm">
<FluentValidationValidator DisableAssemblyScanning="@true" />
<FluentValidationValidator @ref="fluentValidationValidator" DisableAssemblyScanning="@true" />
<ValidationSummary />

<p>
<label>Name: </label>
<InputText @bind-Value="@Person.Name" />
<ValidationMessage For="@(() => Person.Name)" />
<label>First Name: </label>
<InputText @bind-Value="@Person.FirstName" />
<ValidationMessage For="@(() => Person.FirstName)" />
</p>

<p>
<label>Last Name: </label>
<InputText @bind-Value="@Person.LastName" />
<ValidationMessage For="@(() => Person.LastName)" />
</p>

<hr />

<p>
<label>Age: </label>
<InputNumber @bind-Value="@Person.Age" />
Expand Down Expand Up @@ -58,12 +66,21 @@
<button type="submit">Save</button>

</EditForm>
<br />
<button @onclick="PartialValidate">Partial Validation</button>

@code {
Person Person { get; set; } = new Person();

private FluentValidationValidator fluentValidationValidator;

void SubmitValidForm()
{
Console.WriteLine("Form Submitted Successfully!");
}

void PartialValidate()
{
Console.WriteLine($"Partial validation result : {fluentValidationValidator.Validate(options => options.IncludeRuleSets("Names"))}");
}
}
2 changes: 1 addition & 1 deletion samples/BlazorWebAssembly/BlazorWebAssembly.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.0.0" />
<PackageReference Include="FluentValidation" Version="9.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.1" PrivateAssets="all" />
Expand Down
27 changes: 22 additions & 5 deletions samples/BlazorWebAssembly/Pages/Index.razor
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
@page "/"

<h1>Blazored FluentValidation</h1>
<h1>Blazored FluentValidation Sample</h1>

<hr class="mb-5" />

<EditForm Model="@Person" OnValidSubmit="@SubmitValidForm">
<FluentValidationValidator />
<FluentValidationValidator @ref="fluentValidationValidator" />
<ValidationSummary />

<p>
<label>Name: </label>
<InputText @bind-Value="@Person.Name" />
<ValidationMessage For="@(() => Person.Name)" />
<label>First Name: </label>
<InputText @bind-Value="@Person.FirstName" />
<ValidationMessage For="@(() => Person.FirstName)" />
</p>

<p>
<label>Last Name: </label>
<InputText @bind-Value="@Person.LastName" />
<ValidationMessage For="@(() => Person.LastName)" />
</p>

<hr />

<p>
<label>Age: </label>
<InputNumber @bind-Value="@Person.Age" />
Expand Down Expand Up @@ -58,12 +66,21 @@
<button type="submit">Save</button>

</EditForm>
<br />
<button @onclick="PartialValidate">Partial Validation</button>

@code {
Person Person { get; set; } = new Person();

private FluentValidationValidator fluentValidationValidator;

void SubmitValidForm()
{
Console.WriteLine("Form Submitted Successfully!");
}

void PartialValidate()
{
Console.WriteLine($"Partial validation result : {fluentValidationValidator.Validate(options => options.IncludeRuleSets("Names"))}");
}
}
35 changes: 23 additions & 12 deletions samples/Shared/SharedModels/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace SharedModels
{
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
public string EmailAddress { get; set; }
public Address Address { get; set; } = new Address();
Expand All @@ -15,24 +16,34 @@ public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.Name).NotEmpty().WithMessage("You must enter a name");
RuleFor(p => p.Name).MaximumLength(50).WithMessage("Name cannot be longer than 50 characters");
RuleFor(p => p.Age).NotNull().GreaterThanOrEqualTo(0).WithMessage("Age must be greater than 0");
RuleFor(p => p.Age).LessThan(150).WithMessage("Age cannot be greater than 150");
RuleFor(p => p.EmailAddress).NotEmpty().WithMessage("You must enter a email address");
RuleFor(p => p.EmailAddress).EmailAddress().WithMessage("You must provide a valid email address");
RuleSet("Names", () =>
{
RuleFor(p => p.FirstName)
.NotEmpty().WithMessage("You must enter your first name")
.MaximumLength(50).WithMessage("First name cannot be longer than 50 characters");

RuleFor(x => x.Name).MustAsync(async (name, cancellationToken) => await IsUniqueAsync(name))
.WithMessage("Name must be unique")
.When(person => !string.IsNullOrEmpty(person.Name));
RuleFor(p => p.LastName)
.NotEmpty().WithMessage("You must enter your last name")
.MaximumLength(50).WithMessage("Last name cannot be longer than 50 characters");
});

RuleFor(p => p.Age)
.NotNull().WithMessage("You must enter your age")
.GreaterThanOrEqualTo(0).WithMessage("Age must be greater than 0")
.LessThan(150).WithMessage("Age cannot be greater than 150");

RuleFor(p => p.EmailAddress)
.NotEmpty().WithMessage("You must enter a email address")
.EmailAddress().WithMessage("You must provide a valid email address")
.MustAsync(async (email, cancellationToken) => await IsUniqueAsync(email)).WithMessage("Email address must be unique").When(p => !string.IsNullOrEmpty(p.EmailAddress));

RuleFor(p => p.Address).SetValidator(new AddressValidator());
}

private async Task<bool> IsUniqueAsync(string name)
private async Task<bool> IsUniqueAsync(string email)
{
await Task.Delay(300);
return name.ToLower() != "test";
return email.ToLower() != "[email protected]";
}
}
}
2 changes: 1 addition & 1 deletion samples/Shared/SharedModels/SharedModels.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.0.0" />
<PackageReference Include="FluentValidation" Version="9.2.2" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.5" />
<PackageReference Include="FluentValidation" Version="9.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1.9" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.1.9" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using static FluentValidation.AssemblyScanner;

namespace Blazored.FluentValidation
Expand All @@ -18,7 +17,8 @@ public static class EditContextFluentValidationExtensions

private static List<AssemblyScanResult> assemblyScanResults = new List<AssemblyScanResult>();

public static EditContext AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider, bool disableAssemblyScanning, IValidator validator)
public static EditContext AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider, bool disableAssemblyScanning, IValidator validator, FluentValidationValidator fluentValidationValidator)

{
if (editContext == null)
{
Expand All @@ -28,7 +28,7 @@ public static EditContext AddFluentValidation(this EditContext editContext, ISer
var messages = new ValidationMessageStore(editContext);

editContext.OnValidationRequested +=
(sender, eventArgs) => ValidateModel((EditContext)sender, messages, serviceProvider, disableAssemblyScanning, validator);
(sender, eventArgs) => ValidateModel((EditContext)sender, messages, serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator);

editContext.OnFieldChanged +=
(sender, eventArgs) => ValidateField(editContext, messages, eventArgs.FieldIdentifier, serviceProvider, disableAssemblyScanning, validator);
Expand All @@ -40,13 +40,14 @@ private static async void ValidateModel(EditContext editContext,
ValidationMessageStore messages,
IServiceProvider serviceProvider,
bool disableAssemblyScanning,
FluentValidationValidator fluentValidationValidator,
IValidator validator = null)
{
validator = validator ?? GetValidatorForModel(serviceProvider, editContext.Model, disableAssemblyScanning);
validator ??= GetValidatorForModel(serviceProvider, editContext.Model, disableAssemblyScanning);

if (validator is object)
{
var context = new ValidationContext<object>(editContext.Model);
var context = ValidationContext<object>.CreateWithOptions(editContext.Model, fluentValidationValidator.options ?? (opt => opt.IncludeAllRuleSets()));

var validationResults = await validator.ValidateAsync(context);

Expand All @@ -71,7 +72,7 @@ private static async void ValidateField(EditContext editContext,
var properties = new[] { fieldIdentifier.FieldName };
var context = new ValidationContext<object>(fieldIdentifier.Model, new PropertyChain(), new MemberNameValidatorSelector(properties));

validator = validator ?? GetValidatorForModel(serviceProvider, fieldIdentifier.Model, disableAssemblyScanning);
validator ??= GetValidatorForModel(serviceProvider, fieldIdentifier.Model, disableAssemblyScanning);

if (validator is object)
{
Expand All @@ -89,9 +90,15 @@ private static IValidator GetValidatorForModel(IServiceProvider serviceProvider,
var validatorType = typeof(IValidator<>).MakeGenericType(model.GetType());
if (serviceProvider != null)
{
if (serviceProvider.GetService(validatorType) is IValidator validator)
try
{
if (serviceProvider.GetService(validatorType) is IValidator validator)
{
return validator;
}
}
catch (Exception)
{
return validator;
}
}

Expand Down Expand Up @@ -156,13 +163,13 @@ private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string
// 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");

if (prop is object)
{
// we've got an Item property
var indexerType = prop.GetIndexParameters()[0].ParameterType;
var indexerValue = Convert.ChangeType(nextToken, indexerType);
newObj = prop.GetValue(obj, new object[] { indexerValue });
newObj = prop.GetValue(obj, new object[] { indexerValue });
}
else
{
Expand Down
18 changes: 17 additions & 1 deletion src/Blazored.FluentValidation/FluentValidationsValidator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentValidation;
using FluentValidation.Internal;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
Expand All @@ -14,6 +15,21 @@ public class FluentValidationValidator : ComponentBase
[Parameter] public IValidator Validator { get; set; }
[Parameter] public bool DisableAssemblyScanning { get; set; }

internal Action<ValidationStrategy<object>> options;

public bool Validate(Action<ValidationStrategy<object>> options)
{
this.options = options;

try
{
return CurrentEditContext.Validate();
}
finally
{
this.options = null;
}
}

protected override void OnInitialized()
{
Expand All @@ -24,7 +40,7 @@ protected override void OnInitialized()
$"inside an {nameof(EditForm)}.");
}

CurrentEditContext.AddFluentValidation(ServiceProvider, DisableAssemblyScanning, Validator);
CurrentEditContext.AddFluentValidation(ServiceProvider, DisableAssemblyScanning, Validator, this);
}
}
}

0 comments on commit e88697f

Please sign in to comment.