Reputation: 2865
I have a .net 6 WebApi which I am using Fluent Validation with MediatR. I have everything working when there is no validation errors.
When I force an error I get the following Exception.
Unable to cast object of type 'System.Collections.Generic.Dictionary`2[System.String,System.String[]]' to type 'System.Collections.Generic.IEnumerable`1[FluentValidation.Results.ValidationFailure]'.
at TestMediatR.Behaviours.ValidationBehaviour`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
at WebApi.Controllers.v1.OrdersController.AddOrder(OrderTicketDto model) in D:\Git Repositories\Current\Web-Sites\RestWebApi\src\WebApi\Controllers\v1\OrdersController.cs:line 36
Code executing the mediatR send is this.
[HttpPost("AddOrder")]
public async Task<IActionResult> AddOrder([FromBody] OrderTicketDto model)
{
_logger.LogInformation("Adding Order: {@model}", model);
try
{
var response = await Mediator.Send(new AddOrderCommand()
{
OrderData = model.OrderTicket,
Url = model.SiteUrl,
Token = model.Token
});
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Add Order Error"); //<------ FluentValidation exception caught here
return BadRequest(ex.Message);
}
}
and validation for the command executed above is done like this
public class AddOrderCommandValidator : AbstractValidator<AddOrderCommand>
{
public AddOrderCommandValidator()
{
RuleFor(x => x.Url)
.NotEmpty()
.NotNull();
RuleFor(x => x.Token)
.NotEmpty()
.NotNull();
RuleFor(x => x.OrderData)
.NotNull();
}
}
Register of the validators is done here in startup
public static IServiceCollection AddPiKSRestValidators(this IServiceCollection services)
{
var domainAssembly = typeof(GetTablesCommandValidator).GetTypeInfo().Assembly;
//Add FluentValidation
services.AddValidatorsFromAssembly(domainAssembly);
return services;
}
As I say, everything works when I pass valid properties, but force it to be invalid by setting say Token
property to null and I get the exception.
Feel like I am missing something.
Upvotes: 1
Views: 3310
Reputation: 2865
So the issue was with the ValidationBehaviour
here is the code to report the errors
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull, IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
private readonly ILogger<TRequest> _logger;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators, ILogger<TRequest> logger)
{
_validators = validators;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var errorsDictionary = _validators
.Select(x => x.Validate(context))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.GroupBy(
x => x.PropertyName,
x => x.ErrorMessage,
(propertyName, errorMessages) => new
{
Key = propertyName,
Values = errorMessages.Distinct().ToArray()
})
.ToDictionary(x => x.Key, x => x.Values);
if (errorsDictionary.Any())
{
throw new ValidationException((IEnumerable<FluentValidation.Results.ValidationFailure>)errorsDictionary);
}
}
else
_logger.LogDebug("No Validators found");
return await next();
}
}
As you can see a dictionary is trying to be cast to (IEnumerable<FluentValidation.Results.ValidationFailure>)
fixed with this.
var errorsDictionary = _validators
.Select(x => x.Validate(context))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.GroupBy(x => new {x.PropertyName, x.ErrorMessage })
.Select(x => x.FirstOrDefault())
.ToList();
if (errorsDictionary.Any())
{
throw new ValidationException(errorsDictionary);
}
Upvotes: 1