Skip to content

Commit

Permalink
Only execute rules for property when validating at field level
Browse files Browse the repository at this point in the history
  • Loading branch information
matthetherington-pa committed Dec 6, 2023
1 parent a76ede9 commit d8d4443
Showing 1 changed file with 33 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using FluentValidation;
using FluentValidation.Internal;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.DependencyInjection;
using static FluentValidation.AssemblyScanner;
Expand All @@ -13,23 +14,20 @@ public static class EditContextFluentValidationExtensions
private static readonly List<AssemblyScanResult> AssemblyScanResults = new();
public const string PendingAsyncValidation = "AsyncValidationTask";

public static void AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider,
bool disableAssemblyScanning, IValidator? validator, FluentValidationValidator fluentValidationValidator)
public static void AddFluentValidation(this EditContext editContext, IServiceProvider serviceProvider, bool disableAssemblyScanning, IValidator? validator, FluentValidationValidator fluentValidationValidator)
{
ArgumentNullException.ThrowIfNull(editContext, nameof(editContext));

ValidatorOptions.Global.ValidatorSelectors.CompositeValidatorSelectorFactory =
(selectors) => new IntersectingCompositeValidatorSelector(selectors);

var messages = new ValidationMessageStore(editContext);

editContext.OnValidationRequested +=
async (sender, _) => await ValidateModel((EditContext)sender!, messages, serviceProvider,
disableAssemblyScanning, fluentValidationValidator, validator);

async (sender, _) => await ValidateModel((EditContext)sender!, messages, serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator);

editContext.OnFieldChanged +=
async (_, eventArgs) => await ValidateField(editContext, messages, eventArgs.FieldIdentifier,
serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator);
async (_, eventArgs) => await ValidateField(editContext, messages, eventArgs.FieldIdentifier, serviceProvider, disableAssemblyScanning, fluentValidationValidator, validator);
}

private static async Task ValidateModel(EditContext editContext,
Expand Down Expand Up @@ -87,38 +85,36 @@ private static async Task ValidateField(EditContext editContext,
{
return;
}

ValidationContext<object> context;

if (fluentValidationValidator.ValidateOptions is not null)
{
context = ValidationContext<object>.CreateWithOptions(editContext.Model, (options) =>
{
fluentValidationValidator.ValidateOptions(options);
options.IncludeProperties(propertyPath);
});
context = ValidationContext<object>.CreateWithOptions(editContext.Model, fluentValidationValidator.ValidateOptions);
}
else if (fluentValidationValidator.Options is not null)
{
context = ValidationContext<object>.CreateWithOptions(editContext.Model, (options) =>
{
fluentValidationValidator.Options(options);
options.IncludeProperties(propertyPath);
});
context = ValidationContext<object>.CreateWithOptions(editContext.Model, fluentValidationValidator.Options);
}
else
{
context = ValidationContext<object>.CreateWithOptions(editContext.Model, (options) =>
{
options.IncludeProperties(propertyPath);
});
context = new ValidationContext<object>(editContext.Model);
}

validator ??= GetValidatorForModel(serviceProvider, editContext.Model, disableAssemblyScanning);
var fluentValidationValidatorSelector = context.Selector;
var changedPropertySelector = ValidationContext<object>.CreateWithOptions(editContext.Model, strategy =>
{
strategy.IncludeProperties(propertyPath);
}).Selector;

var compositeSelector =
new IntersectingCompositeValidatorSelector(new IValidatorSelector[] { fluentValidationValidatorSelector, changedPropertySelector });

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

if (validator is not null)
{
var validationResults = await validator.ValidateAsync(context);
var validationResults = await validator.ValidateAsync(new ValidationContext<object>(editContext.Model, new PropertyChain(), compositeSelector));
var errorMessages = validationResults.Errors
.Select(validationFailure => validationFailure.ErrorMessage)
.Distinct();
Expand All @@ -132,12 +128,12 @@ private static async Task ValidateField(EditContext editContext,

private class Node
{
public object ModelObject { get; set; }
public Node? Parent { get; set; }
public object ModelObject { get; set; }
public string? PropertyName { get; set; }
public int? Index { get; set; }
}

private static string ToFluentPropertyPath(EditContext editContext, FieldIdentifier fieldIdentifier)
{
var nodes = new Stack<Node>();
Expand All @@ -155,7 +151,7 @@ private static string ToFluentPropertyPath(EditContext editContext, FieldIdentif
{
return BuildPropertyPath(currentNode, fieldIdentifier);
}

var nonPrimitiveProperties = currentModelObject
.GetType()
.GetProperties()
Expand All @@ -173,11 +169,11 @@ private static string ToFluentPropertyPath(EditContext editContext, FieldIdentif
PropertyName = nonPrimitiveProperty.Name,
ModelObject = instance
};

return BuildPropertyPath(node, fieldIdentifier);
}

if (instance is IEnumerable enumerable)
if(instance is IEnumerable enumerable)
{
var itemIndex = 0;
foreach (var item in enumerable)
Expand All @@ -191,7 +187,7 @@ private static string ToFluentPropertyPath(EditContext editContext, FieldIdentif
});
}
}
else if (instance is not null)
else if(instance is not null)
{
nodes.Push(new Node()
{
Expand Down Expand Up @@ -234,8 +230,7 @@ private static string BuildPropertyPath(Node currentNode, FieldIdentifier fieldI
return string.Join('.', pathParts);
}

private static IValidator? GetValidatorForModel(IServiceProvider serviceProvider, object model,
bool disableAssemblyScanning)
private static IValidator? GetValidatorForModel(IServiceProvider serviceProvider, object model, bool disableAssemblyScanning)
{
var validatorType = typeof(IValidator<>).MakeGenericType(model.GetType());
try
Expand Down Expand Up @@ -293,7 +288,7 @@ private static FieldIdentifier ToFieldIdentifier(in EditContext editContext, in

var obj = editContext.Model;
var nextTokenEnd = propertyPath.IndexOfAny(Separators);

// Optimize for a scenario when parsing isn't needed.
if (nextTokenEnd < 0)
{
Expand All @@ -320,8 +315,8 @@ private static FieldIdentifier ToFieldIdentifier(in EditContext editContext, in
// we've got an Item property
var indexerType = prop.GetIndexParameters()[0].ParameterType;
var indexerValue = Convert.ChangeType(nextToken.ToString(), indexerType);

newObj = prop.GetValue(obj, new[] { indexerValue });
newObj = prop.GetValue(obj, new [] { indexerValue });
}
else
{
Expand All @@ -346,7 +341,6 @@ private static FieldIdentifier ToFieldIdentifier(in EditContext editContext, in
{
throw new InvalidOperationException($"Could not find property named {nextToken.ToString()} on object of type {obj.GetType().FullName}.");
}

newObj = prop.GetValue(obj);
}

Expand All @@ -357,7 +351,7 @@ private static FieldIdentifier ToFieldIdentifier(in EditContext editContext, in
}

obj = newObj;

nextTokenEnd = propertyPathAsSpan.IndexOfAny(Separators);
if (nextTokenEnd < 0)
{
Expand Down

0 comments on commit d8d4443

Please sign in to comment.