I have a MediatR Pipeline behavior for validating commands with the FluentValidation library.
All the examples that I came across were throwing ValidationException if any failures happening, instead of doing that I want to return the response with the error.
Here I want to have response model
[{
errorMesages:[]
status:404
ErrorField:propertyName
}]
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return next();
}
}
try use this instead
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new FluentValidation.ValidationException(failures);
}
return await next();
}
Related
We are using a MediatR pipeline behavior with fluent validation but for some reason one of our validator classes is not working with the pipeline behaviour. Here is the misbehaving validator...
public class LinkProfilePhotoQueryValidator : AbstractValidator<LinkProfilePhotoQuery>
{
public LinkProfilePhotoQueryValidator(
ITableSecurityService tableSecurityService)
{
this.RuleFor(c => c)
.MustAsync(async (record, token) =>
{
var tableSecurity = (
await tableSecurityService.GetTableSecurityForEmployee(
Table.ProfilePictures,
record.EmployeeId,
token)
);
if (tableSecurity.Read.Enabled)
return true;
else
throw new PermissionDeniedException("You do not have permissions to view this profile photo");
});
}
}
I am getting an error back which says "The validator "LinkProfilePhotoQueryValidator" can't be used with ASP.NET automatic validation as it contains asynchronous rules." and MediatR documetation tells me to use the ValidateAsync when getting this error. We have ValidateAsync setup in a Pipeline behavior but for some reason, and on only this validator alone, it does not use this behavior.
public class RequestValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> validators;
public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
=> this.validators = validators;
public async Task<TResponse> Handle(
TRequest request,
CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var validationResults = new List<ValidationResult>();
foreach (var validator in this.validators)
{
validationResults.Add(await validator.ValidateAsync(context, cancellationToken));
}
var errors = validationResults
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (errors.Count != 0)
{
throw new ModelValidationException(errors);
}
return await next();
}
}
And this is the request handler it should be validating
public class LinkProfilePhotoQuery : IRequest<LinkProfilePhotoOutputModel>
{
public string EmployeeId { get; set; } = default!;
public class LinkProfilePhotoQueryHandler : BaseHandler, IRequestHandler<
LinkProfilePhotoQuery,
LinkProfilePhotoOutputModel>
{
private readonly IFileService fileService;
private readonly IProfilePhotoQueryRepository photoRepository;
public LinkProfilePhotoQueryHandler(
IFileService fileService,
IProfilePhotoQueryRepository photoRepository,
ICurrentUser currentUser,
Domain.Users.Repositories.IUserDomainRepository userRepository) : base(currentUser, userRepository)
{
this.fileService = fileService;
this.photoRepository = photoRepository;
}
public async Task<LinkProfilePhotoOutputModel> Handle(
LinkProfilePhotoQuery request,
CancellationToken cancellationToken)
{
var photo = await photoRepository.GetDetails(
request.EmployeeId,
DateTime.UtcNow,
cancellationToken);
if (photo == null)
throw new NotFoundException("EmployeeId", request.EmployeeId);
var stream = await fileService.GetFile(
StorageContainer.Talent,
photo.FileNameOnDisk,
cancellationToken);
if (stream == null)
throw new NotFoundException("Photo", request.EmployeeId);
return new LinkProfilePhotoOutputModel($"data:{photo.ContentType};base64,{Convert.ToBase64String(stream.ToArray())}");
}
}
}
I have an issue that my FluentValidations does not work on my Clean (Onion) Architecture.
It checks in my AddVehicleCommand class, and checks some rules, but does not apply them (it goes to the repository, and work as there no validations at all)
Here is the usage of FluentValidations
public class AddVehicleCommandValidator : AbstractValidator<AddVehicleCommand>
{
private readonly IVehicleRepositoryAsync _repositoryVehicle;
public AddVehicleCommandValidator(IVehicleRepositoryAsync repositoryVehicle)
{
_repositoryVehicle = repositoryVehicle;
RuleFor(x => x.Vehicle.Model).NotNull().WithMessage("Model is required.");
RuleFor(x => x.Vehicle.Maker).NotNull().WithMessage("Maker is required.");
RuleFor(x => x.Vehicle.UniqueId).NotNull().WithMessage("UniqueId is required").Must(CheckKeyComposition).WithMessage("UniqueId needs to be in fomrat C<number>");
RuleFor(x => x.Vehicle.UniqueId).MustAsync((x, cancellation) => AlreadyInUse(x)).WithMessage("This id is already in use.");
}
private async Task<bool> AlreadyInUse(string key)
{
var entity = await _repositoryVehicle.GetById(key);
if(entity == null)
{
return true;
}
var id = entity.UniqueId;
if(key == id)
{
return true;
}
return false;
}
private bool CheckKeyComposition(string key)
{
var firstChar = key.Substring(0);
var secondChar = key.Substring(1, key.Length-1);
int number;
bool res = int.TryParse(secondChar, out number);
if (firstChar.Equals("C") && res)
{
return true;
}
else
{
return false;
}
}
}
and I implemented Behavior for this FLuentValidation:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
}
return await next();
}
}
Also I have registered FLuentValidation in my ServiceExtension class (DI):
public static class ServiceExtension
{
public static void AddApplicationLayer(this IServiceCollection services)
{
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
}
The example of calling logic for AddVehicleCommand:
public class AddVehicleCommand : IRequest<Result<VehicleDto>>
{
public VehicleDto? Vehicle { get; set; }
}
public class AddVehicleCommandHanlder : IRequestHandler<AddVehicleCommand, Result<VehicleDto>>
{
private readonly IVehicleRepositoryAsync _vehicleRepository;
private IMapper _mapper;
public AddVehicleCommandHanlder(IVehicleRepositoryAsync vehicleRepository, IMapper mapper)
{
_vehicleRepository = vehicleRepository;
_mapper = mapper;
}
public async Task<Result<VehicleDto>> Handle(AddVehicleCommand request, CancellationToken cancellationToken)
{
Result<VehicleDto> result = new();
try
{
if (request.Vehicle != null)
{
VehicleDto vehicle = new();
vehicle.Maker = request.Vehicle.Maker;
vehicle.Model = request.Vehicle.Model;
vehicle.UniqueId = await getNewId();
Vehicle entity = _mapper.Map<Vehicle>(vehicle);
var response = await _vehicleRepository.AddAsync(entity);
result.Success = true;
result.StatusCode = System.Net.HttpStatusCode.OK;
result.Data = vehicle;
return result;
}
}
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
result.StatusCode = System.Net.HttpStatusCode.InternalServerError;
result.Success = false;
return result;
}
result.ErrorMessage = "Bad request.";
result.StatusCode = System.Net.HttpStatusCode.BadRequest;
result.Success = false;
return result;
}
private async Task<string> getNewId()
{
var latestId = await getLatestFreeId();
var newId = (Convert.ToInt32(latestId.Substring(1, latestId.Length-1)) + 1).ToString();
return string.Format("C{0}", newId);
}
private async Task<string> getLatestFreeId()
{
var latestId = await _vehicleRepository.GetLatestFreeId();
if (string.IsNullOrEmpty(latestId))
{
//default Vehicle Id
return "C0";
}
return latestId;
}
}
It hit the validator, but doesnt apply it (does not return any error, but success code). Why?
UPDATE#1:
I partially succeeded to present errors with this Behavior:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new FluentValidation.ValidationException(failures);
}
return next();
}
}
Also, I changed the registration of fluentvalidation:
public static IServiceCollection AddApplicationLayer(this IServiceCollection services)
{
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddAutoMapper(Assembly.GetExecutingAssembly());
return services;
}
But Status code is 500, and I would like to be 400 BadRequest, also, I would like to have better preview in some kind of list or something
I have this ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationBehavior(IValidator<TRequest> validator)
{
_validator = validator;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_validator.ValidateAndThrow(request);
return await next();
}
}
I have this handler
public class RemoveCurrencyHandler : IRequestHandler<RemoveCurrencyCommand, Unit>
{
private readonly ApplicationContext _context;
public RemoveCurrencyHandler(ApplicationContext context)
{
_context = context;
}
public async Task<Unit> Handle(RemoveCurrencyCommand request, CancellationToken cancellationToken)
{
var currency = await _context.Currency.FindAsync(request.Id);
if (currency is null)
throw new KeyNotFoundException();
_context.Remove(currency);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
I'm getting error message Unable to resolve service for type 'FluentValidation.IValidator' everytime I call this handler, now obviously I know the reason is because I'm missing the validator, so it goes away if I add this
public class RemoveCurrencyValidator : AbstractValidator<RemoveCurrencyCommand>
{
}
but not all my handler need a Validator, so I don't want to add empty Validator class to handler that doesn't need it. Is there any alternative?
The MediatR validator pieplines enable you to execute validation logic before and after your Command or Query Handlers execute.
You can update the constructor of ValidationBehavior class to expect an IEnumerable<IValidator>. So that we can always resolve the class (the list will be empty if you don't implement the Validator)
// ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumrable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if(_validators.Any())
{
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAndThrow(request)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
I am currently working with Pipeline behavior in Mediatr 3 for request validation. All the examples that I came across were throwing ValidationException if any failures happening, instead of doing that I want to return the response with the error. Anyone has idea on how to do it?
Below is the code for the validation pipeline:
public class ValidationPipeline<TRequest, TResponse> :
IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationPipeline(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return next();
}
}
Note: I found this question Handling errors/exceptions in a mediator pipeline using CQRS? and I am interested in the 1st option on the answer, but no clear example on how to do that.
This is my response class:
public class ResponseBase : ValidationResult
{
public ResponseBase() : base() { }
public ResponseBase(IEnumerable<ValidationFailure> failures) : base(failures) {
}
}
and I added below signature in the validation pipeline class:
public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
where TResponse : ResponseBase
I did this then in the Handle method:
var response = new ResponseBase(failures);
return Task.FromResult<TResponse>(response);
But that gave me error 'cannot convert to TResponse'.
Several years ago, I created general Result object, which I am constantly improving. It is quite simple, check https://github.com/martinbrabec/mbtools.
If you will be ok with the Result (or Result<>) being the return type every method in Application layer, then you can use the ValidationBehavior like this:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Result, new()
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext(request);
List<ValidationFailure> failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
TResponse response = new TResponse();
response.Set(ErrorType.NotValid, failures.Select(s => s.ErrorMessage), null);
return Task.FromResult<TResponse>(response);
}
else
{
return next();
}
}
return next();
}
}
Since all your handlers return Result (or Result<>, which is based upon Result), you will be able to handle all validation errors without any exception.
Simply don't call next if there's any failures:
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
var response = new Thing(); //obviously a type conforming to TResponse
response.Failures = failures; //I'm making an assumption on the property name here.
return Task.FromResult(response);
}
else
{
return next();
}
}
Note:
Your class (Thing in my example) must be of type TResponse
You can configure validation handling using package
https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore
Just insert in configuration section:
services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});
GitHub
I'm using ASP.NET Core, the built-in container, and MediatR 3 which supports "behavior" pipelines:
public class MyRequest : IRequest<string>
{
// ...
}
public class MyRequestHandler : IRequestHandler<MyRequest, string>
{
public string Handle(MyRequest message)
{
return "Hello!";
}
}
public class MyPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
return response;
}
}
// in `Startup.ConfigureServices()`:
services.AddTransient(typeof(IPipelineBehavior<MyRequest,string>), typeof(MyPipeline<MyRequest,string>))
I need a FluentValidation validator in the pipeline. In MediatR 2, a validation pipeline was created thus:
public class ValidationPipeline<TRequest, TResponse>
: IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public ValidationPipeline(IRequestHandler<TRequest, TResponse> inner, IEnumerable<IValidator<TRequest>> validators)
{
_inner = inner;
_validators = validators;
}
public TResponse Handle(TRequest message)
{
var failures = _validators
.Select(v => v.Validate(message))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
return _inner.Handle(request);
}
}
How do I do that now for the new version? How do I set which validator to use?
The process is exactly the same, you just have to change the interface to use the new IPipelineBehavior<TRequest, TResponse> interface.
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}
For the validators, you should register all the validators as IValidator<TRequest> in the built-in container so they'll be injected in the behavior. If you don't want to register them one by one, I suggest that you have a look at the great Scrutor library that brings assembly scanning capabilities. This way it'll find your validators itself.
Also, with the new system, you don't use the decorator pattern anymore, you just register your generic behavior in the container and MediatR will pick it up automatically. It could look something like:
var services = new ServiceCollection();
services.AddMediatR(typeof(Program));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
var provider = services.BuildServiceProvider();
I've packed .net core integration into nuget, feel free to use it:
https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore
Just insert in configuration section:
services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});
GitHub
On the new version (MediatR (>= 9.0.0)) you can do something like this:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}
Remember to add var context = new ValidationContext<TRequest>(request); in previous version like FluentApi 8.0 or below it used something like this var context = new ValidationContext(request);
for Register in Asp.Net Core in under IServiceCollection wright below code:
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
Hope that's helpful!