0

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 enter image description here

Stefan0309
  • 1,602
  • 5
  • 23
  • 61
  • Your behaviour should return an error response if validation fails? – juunas Jan 19 '23 at 09:02
  • Hmm, but here on this SO topic, with accepted answer there is no return statement: https://stackoverflow.com/questions/42283011/add-validation-to-a-mediatr-behavior-pipeline – Stefan0309 Jan 19 '23 at 09:09
  • I guess your DI does not applied well. I mean you should define specific assembly like ( typeof(the class that refers to validations).Assembly) – Ali Khancherli Jan 19 '23 at 09:13
  • Please refer to my update#1. – Stefan0309 Jan 19 '23 at 09:18
  • You need to return the result you want from the behaviour. Same as you would return from the handler if you wanted a 400 bad request. – juunas Jan 19 '23 at 09:18
  • You mean to wrap something like Result? But then I cant add this type of return method in my `.Must()` chain? Can you give me just a short example? tnx – Stefan0309 Jan 19 '23 at 09:21

0 Answers0