1

I migrated my web api from asp net core 3.1 to .net 6 and I'm having some issues with the using FluentValidation library.

RuleFor(x => x.Name)
            .NotEmpty()
            .OnFailure(x =>
            {
                var message = "El nombre del Usuario es requerido.";
                throw new HttpException(message, message, (int)HttpStatusCode.BadRequest);
            })
            .Length(1, 150)
            .OnFailure(x =>
            {
                var message = "El nombre del Usuario debe contener hasta máximo 150 caracteres.";
                throw new HttpException(message, message, (int)HttpStatusCode.BadRequest);
            });

This code used to work correctly but, when I updated the library , the method .OnFailure() is deprecated.

I would like to know how can I throw a custom exception for each OnFailure as I used to do it, where HttpException is a custom class that inherited from Exception. An example of json result would be:

    {
  "developerMessage": "El nombre del Usuario es requerido..",
  "userMessage": "El nombre del Usuario es requerido.",
  "errorCode": 400,
  "moreInfo": ""
   }

UPDATED!

I followed this article https://www.tutorialspoint.com/what-is-use-of-fluent-validation-in-chash-and-how-to-use-in-chash. I used CascadeMode.Stop because the previous one is deprecated but, when I try testing .Lenght validation, the result variable shows the firt error of each RuleFor(). I mean: If I have 3 RuleFor() and each rule has 2 validators, the result variable shows the first error message's validator of each RuleFor() and no error message of the real failure.

How can I solve this?

public UserValidator()
{
  RuleFor(x => x.Name)
                .Cascade(CascadeMode.Stop)
                .NotEmpty().WithMessage("El nombre del Usuario es requerido.")
                .Length(1, 150).WithMessage("El nombre del Usuario debe contener hasta máximo 150 caracteres.");

  var result = Validate(new User()); //I need to get the context (user) to validate it , **not new User()**
        if(!result.IsValid)
        {
            foreach(var error in result.Errors)
            {
                var message = $"{error.ErrorMessage}";
                throw new HttpException(message, message, (int)HttpStatusCode.BadRequest);
            }
        }
}
JohanEcAv
  • 59
  • 5
  • Have you considered using `WithMessage()` instead of `OnFailure()` and then throwing the exception if model state is invalid in a [custom action filter](https://stackoverflow.com/a/69582675/8065832)? – Prolog Apr 29 '22 at 06:51

1 Answers1

0

You can use the ValidateAndThrow method of your validation class that comes by inheriting from the AbstractValidator<T> class.

In your case, as you want to throw the HttpException exception, you must override the RaiseValidationException method of your validation classes so that they can throw the desired exception.

There are two ways to do this:

1 - Overriding the RaiseValidationException method for each validation class:

public class UserValidator : AbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.Name)
            .Cascade(CascadeMode.Stop)
            .NotEmpty()
            .WithMessage("El nombre del Usuario es requerido.")
            .Length(1, 150)
            .WithMessage("El nombre del Usuario debe contener hasta máximo 150 caracteres.");
    }

    // Add this override on your validation class
    protected override void RaiseValidationException(ValidationContext<User> context, ValidationResult result)
    {
        var firstError = result.Errors[0];

        var ex = new HttpExcpetion(firstError.ErrorMessage, (int)HttpStatusCode.BadRequest);

        throw ex;
    }
}

2 - Creating an abstract class with the method overriding so that there is no need to re-implement each validation class:

public abstract class CustomAbstractValidator<T> : AbstractValidator<T>
{
    protected override void RaiseValidationException(ValidationContext<T> context, ValidationResult result)
    {
        var firstError = result.Errors[0];

        var ex = new HttpExcpetion(firstError.ErrorMessage, (int)HttpStatusCode.BadRequest);

        throw ex;
    }
}

public class UserValidator : CustomAbstractValidator<User>
{
    public UserValidator()
    {
        RuleFor(x => x.Name)
            .Cascade(CascadeMode.Stop)
            .NotEmpty()
            .WithMessage("El nombre del Usuario es requerido.")
            .Length(1, 150)
            .WithMessage("El nombre del Usuario debe contener hasta máximo 150 caracteres.");
    }
}

After implementing it, just get your validator instance and call the ValidateAndThrow method:

1 - Without dependency injection:

public class YourProgramFlowClass
{
    public void YourMethod()
    {
        // ... before validation code

        var validator = new UserValidator();

        validator.ValidateAndThrow(user);

        // ... after validation code
    }
}

2 - Without dependency injection:

public class YourProgramFlowClass
{
    private readonly IValidator<ClassToValidate> _validator;

    public YourProgramFlowClass(IValidator<ClassToValidate> validator)
    {
        _validator = validator;
    }

    public void YourMethod()
    {
        // ... before validation code

        _validator.ValidateAndThrow(user);

        // ... after validation code
    }
}