Validator not working with MediatR Pipeline - c#

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())}");
}
}
}

Related

FLuentValidations doesn't work in Onion (Clean) Architecture

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

Fluent Validation Custom response using pipeline behavior

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();
}

MediatR PipelineBehavior error message Unable to resolve service for type 'FluentValidation.IValidator`1

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();
}
}

How to call async from sync method in blazor and wait for result?

In Blazor webassembly I have javascript function in js file:
function AksMessage(message) {
return confirm(message);
}
In razor file:
[Inject]
public IJSRuntime JSRuntime { get; set; }
public async Task<bool> askMessage(msg)
{
await JSRuntime.InvokeVoidAsync("AskMessage", msg);
}
Now in some not async function I want to call
askMessage and get result if user clicked and returned false or true.
How can I run it and wait for result from synchronous part of code?
If I do:
var askmsg = Task.Run(async () => await askMessage("question"));
and askmsg.Result I have exception that monitors can not wait on this runtime.
I just solved this for setting bearer tokens on a gRPC interceptor which required some async code in WASM, you do it like this:
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var call = continuation(request, context);
return new AsyncUnaryCall<TResponse>(
call.ResponseAsync,
GetMetadata(),
call.GetStatus,
call.GetTrailers,
call.Dispose);
}
private async Task<Metadata> GetMetadata()
{
try
{
if (!string.IsNullOrEmpty(await _authenticationManager.TryRefreshToken()))
_snackbar.Add("Refreshed Token.", Severity.Success);
var token = _authenticationManager.Token;
var metadata = new Metadata();
if (!string.IsNullOrEmpty(token))
{
metadata.Add("Authorization", $"Bearer {token}");
var userIdentity = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;
if (userIdentity!.IsAuthenticated)
metadata.Add("User", userIdentity.Name!);
}
else
{
_authenticationManager.Logout().GetAwaiter().GetResult();
_navigationManager.NavigateTo("/");
}
return metadata;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to add token to request metadata", ex);
}
}
EDIT: For some reason, although this async code runs, it doesn't actually set the headers onto the request so you have to do the following hack:
public class AuthenticationInterceptor : Interceptor
{
private readonly IAuthenticationManager _authenticationManager;
private readonly ISnackbar _snackbar;
private readonly NavigationManager _navigationManager;
private readonly AuthenticationStateProvider _authenticationStateProvider;
private Metadata? _metadata;
public AuthenticationInterceptor(
IAuthenticationManager authenticationManager,
ISnackbar snackbar,
NavigationManager navigationManager,
AuthenticationStateProvider authenticationStateProvider)
{
_authenticationManager = authenticationManager;
_snackbar = snackbar;
_navigationManager = navigationManager;
_authenticationStateProvider = authenticationStateProvider;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
_ = new AsyncUnaryCall<TResponse>(
null!,
GetMetadata(context),
null!,
null!,
null!); // Doesn't actually send the request but runs the Task
// that provides the metadata to the field which can
// then be used to set the request headers
var newOptions = context.Options.WithHeaders(_metadata!);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method,
context.Host,
newOptions);
return base.AsyncUnaryCall(request, newContext, continuation);
}
private async Task<Metadata> GetMetadata<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context)
where TRequest : class
where TResponse : class
{
try
{
if (!string.IsNullOrEmpty(await _authenticationManager.TryRefreshToken()))
_snackbar.Add("Refreshed Token.", Severity.Success);
var token = _authenticationManager.Token;
Console.WriteLine($"Token: {token}");
var headers = new Metadata();
if (!string.IsNullOrEmpty(token))
{
headers.Add(new Metadata.Entry("Authorization", $"Bearer {token}"));
Console.WriteLine("Set metadata");
var userIdentity = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;
if (userIdentity!.IsAuthenticated)
headers.Add(new Metadata.Entry("User", userIdentity.Name!));
}
else
{
await _authenticationManager.Logout();
_navigationManager.NavigateTo("/");
}
var callOptions = context.Options.WithHeaders(headers);
return _metadata = callOptions.Headers!;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to add token to request headers", ex);
}
}
}
You can call async method from synchronous method and wait for it like this :
var askmsg = Task.Run(async () => await askMessage("question"));
var result = Task.WaitAndUnwrapException();
another solution is like this (sync method backs to its context):
var result = AsyncContext.RunTask(askMessage("question")).Result;

Return response with errors instead of throwing exception in validation pipeline mediatr 3

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

Categories

Resources