skip to Main Content

I’m using ASP, CQRS + MediatR and fluent validation. I want to implement user role validation, but I don’t want to mix it with business logic validation. Do you have any idea how to implement this?
I mean a specific validator must be executed for a specific request.
Something tells me the solution lies in IEnumerable< IValidator>

{
    private readonly IEnumerable<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 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).ToArray();

            if (failures.Any())
            {
                var errors = failures
                    .Select(x => new Error(x.ErrorMessage, x.ErrorCode))
                    .ToArray();
                throw new ValidationException(errors);
            }
        }

        return await next();
    }
}

2

Answers


  1. I see your concern, I also found myself in this situation. I wanted to separate my validators from handlers while also keeping them in the domain/business project. Also I didn’t want to throw exceptions just to handle bad request or any other custom business exception.
    You have the right idea by

    I mean a specific validator must be executed for a specific request

    For this, you need to set up a mediator pipeline, so for every Command you can find the appropriate the appropriate validator, validate and decide whether to execute the command or return a failed result.

    First, create an interface(although not necessary but it is how I did it) of ICommand like this:

    public interface ICommand<TResponse>: IRequest<TResponse>
    {
    
    }
    

    And, ICommandHandler like:

    public interface ICommandHandler<in TCommand, TResponse>: IRequestHandler<TCommand, TResponse>
            where TCommand : ICommand<TResponse>
    {
    
    }
    

    This way we can only apply validation to commands. Instead of iheriting IRequest<MyOutputDTO> and IRequestHandler<MyCommand, MyOutputDTO> you inherit from ICommand and ICommandHandler.

    Now create a ValidationBehaviour for the mediator as we agreed before.

    public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : class, ICommand<TResponse>
    {
        private readonly IEnumerable<IValidator<TRequest>> _validators;
    
        public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
    
        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
        {
            if (!_validators.Any())
                return await next();
    
            var validationContext = new ValidationContext<TRequest>(request);
    
            var errors = (await Task.WhenAll(_validators
                .Select(async x => await x.ValidateAsync(validationContext))))
                .SelectMany(x => x.Errors)
                .Where(x => x != null)
                .Select(x => x.CustomState)
                .Cast<TResponse>();
    
            //TResponse should be of type Result<T>
    
            if (errors.Any())
                return errors.First();
    
            try
            {
               return await next();
            }
            catch(Exception e)
            {
               //most likely internal server error
               //better retain error as an inner exception for debugging
               //but also return that an error occurred
               return Result<TResponse>.Failure(new InternalServerException(e));
            }
        }
    }
    

    This code simply, excepts all the validators in the constructor, because you register all your validator from assembly for your DI container to inject them.
    It waits for all validations to validate async(because my validations mostly require calls to db itself such as getting user roles etc).
    Then check for errors and return the error(here I have created a DTO to wrap my error and value to get consistent results).
    If there were no errors simply let the handler do it’s work return await next();

    Now you have to register this pipeline behavior and all the validators.
    I use autofac so I can do it easily by

    builder
           .RegisterAssemblyTypes(_assemblies.ToArray())
           .AsClosedTypesOf(typeof(IValidator<>))
           .AsImplementedInterfaces();
            var mediatrOpenTypes = new[]
            {
                    typeof(IRequestHandler<,>),
                    typeof(IRequestExceptionHandler<,,>),
                    typeof(IRequestExceptionAction<,>),
                    typeof(INotificationHandler<>),
                    typeof(IPipelineBehavior<,>)
            };
    
            foreach (var mediatrOpenType in mediatrOpenTypes)
            {
                builder
                    .RegisterAssemblyTypes(_assemblies.ToArray())
                    .AsClosedTypesOf(mediatrOpenType)
                    .AsImplementedInterfaces();
            }
    

    If you use Microsoft DI, you can:

    services.AddMediatR(typeof(Application.AssemblyReference).Assembly);
    services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    
    services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly); //to add validators
    

    Example usage:
    My generic DTO Wrapper

    public class Result<T>: IResult<T>
    {
        public Result(T? value, bool isSuccess, Exception? error)
        {
            IsSuccess = isSuccess;
            Value = value;
            Error = error;
        }
    
        public bool IsSuccess { get; set; }
    
        public T? Value { get; set; }
        public Exception? Error { get; set; }
    
    
        public static Result<T> Success(T value) => new (value, true, null);
        public static Result<T> Failure(Exception error) => new (default, false, error);
    }
    

    A sample Command:

    public record CreateNewRecordCommand(int UserId, string record) : ICommand<Result<bool>>;
    

    Validator for it:

    public class CreateNewRecordCommandValidator : AbstractValidator<CreateNewRecordCommand>
    {
        public CreateNewVoucherCommandValidator(DbContext _context, IMediator mediator) //will be injected by out DI container
        {
              RuleFor(x => x.record)
                .NotEmpty()
                .WithState(x => Result<bool>.Failure(new Exception("Empty record")));
              //.WithName("record") if your validation a property in array or something and can't find appropriate property name
    
              RuleFor(x => x.UserId)
                .MustAsync(async(id, cToken) =>
                 {
                       //var roles = await mediator.send(new GetUserRolesQuery(id, cToken));
                       //var roles = (await context.Set<User>.FirstAsync(user => user.id == id)).roles
    
                       //return roles.Contains(MyRolesEnum.CanCreateRecordRole);
                 }
                )
                .WithState(x => Result<bool>.Failure(new MyCustomForbiddenRequestException(id)))
        }
    }
    

    This way you always get a result object, you can check if error is null or !IsSuccess and then create a custom HandleResult(result) method in your Controller base which can switch on the exception to return BadReuqestObjectResult(result) or ForbiddenObjectResult(result).

    If you prefer to throw, catch and handle the exceptions in the pipeline or you wan’t non-async implementation, read this https://code-maze.com/cqrs-mediatr-fluentvalidation/
    This way all your validations are very far from your handler while maintaining consistent results.

    Login or Signup to reply.
  2. I think that your initial approach its right. When you say that you want to keep the auth validations apart from the other business validation, do you mean like returning a http error like 403 and 401 right?
    If thats the case try marking the auth validations with and interface to identify they, and do not run all the validations at once. Search first in the collection for a validation with that interface, and if it fails send a custom exception that you can identity in a IActionFilter to set the wanted result. This code does not do that exactly but you can make an idea.

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        private ISystemLogger _logger;
        public HttpResponseExceptionFilter()
        {
        }
        public int Order { get; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is PipelineValidationException exception)
            {
                context.Result = new ObjectResult(new Response(false, exception.ValidationErrors.FirstOrDefault()?.ErrorMessage ?? I18n.UnknownError));
                context.ExceptionHandled = true;
            }
            else if (context.Exception != null)
            {
                _logger ??= (ISystemLogger)context.HttpContext.RequestServices.GetService(typeof(ISystemLogger));
                _logger?.LogException(this, context.Exception, methodName: context.HttpContext.Request.Method);
                context.Result = new ObjectResult(new Response(false, I18n.UnknownError));
                context.ExceptionHandled = true;
            }
    
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search