Home > Blockchain >  FluentValidation async pre-load data before validating collection
FluentValidation async pre-load data before validating collection

Time:08-25

I'm currently refactoring some validation we perform using an AsyncPropertyValidator which makes an async call to retrieve data from a database against which validation is performed.

This AsyncPropertyValidator is being applied to a collection of items, requiring a roundtrip to the database for every single item as the AsyncPropertyValidator is applied using a RuleForEach.

RuleForEach(r => r.Children).SetAsyncValidator(new CustomValidator<CreateObjectRequest>(_dbContext));

Ideally I'd like to make a single async database call to retrieve data relevant to the whole collection, then pass this data down to the individual item validator allowing individual validation errors to be reported for each invalid item in the collection, rather than providing a single error concerning the collection as a whole.

I haven't managed to find a nice way to preload some data asynchronously before validating individual items.

I did explore making an AsyncPropertyValidator for the collection level object e.g.

public class CustomCollectionValidator : AbstractValidator<IEnumerable<Child>>

but when iterating over the collection in an overridden IsValidAsync method there seems to be no way to add validation error messages to the ValidationContext<T> context for child items, only the parent collection object itself.

I imagine I could possibly use the context.RootContextData to pass this data in, but I can't see any obvious way to async pre-load this data in the parent AbstractValidator before the validation rule is fired.

Another interesting aspect for this scenario is that before the individual items of the collection are validated we perform some gating validation first using DependentRules to make sure too many items aren't being passed in, mitigating against DoS attacks by sending too much data to the API. Only once this gating validation has passed do we move on to any database related validation of individual items.

.DependentRules(() => // Gate database centric validation
{
    RuleForEach(r => r.Children).SetAsyncValidator(new CustomValidator<CreateObjectRequest>(_dbContext));
});

Any ideas about the best way I can solve this problem?

CodePudding user response:

It seems CustomAsync is what I required to allow me to populate RootContextData with values returned from the async database call.

For example:

public class CustomCollectionValidator : AbstractValidator<IEnumerable<Item>>
{
    public const string ContextDataKey = "MyData";

    private readonly IDbRepository _dbRepository;

    public CustomCollectionValidator (IDbRepository dbRepository)
    {
        _dbRepository = dbRepository;

        RuleFor(items => items)
            .CustomAsync(async (items, context, cancellationToken) =>
            {
                var data = await _dbRepository.GetDataAsync(items, cancellationToken);
                context.RootContextData[ContextDataKey] = data;
            });

        RuleForEach(e => e).SetValidator(new CustomValidator<IEnumerable<Item>>());
    }
}

Then in CustomValidator I access the RootContextData value in the IsValid method.

  • Related