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.