Use custom validation responses with fluent validation

As for me, it's better to use the following code in ASP.NET Core project

  services.AddMvc().ConfigureApiBehaviorOptions(options =>
  {
    options.InvalidModelStateResponseFactory = c =>
    {
      var errors = string.Join('\n', c.ModelState.Values.Where(v => v.Errors.Count > 0)
        .SelectMany(v => v.Errors)
        .Select(v => v.ErrorMessage));

      return new BadRequestObjectResult(new
      {
        ErrorCode = "Your validation error code",
        Message = errors
      });
    };
  });

Also take into account that instead of anonymous object you can use your concrete type. For example,

     new BadRequestObjectResult(new ValidationErrorViewModel
      {
        ErrorCode = "Your validation error code",
        Message = errors
      });

In .net core you can use a combination of a IValidatorInterceptor to copy the ValidationResult to HttpContext.Items and then a ActionFilterAttribute to check for the result and return the custom response if it is found.

// If invalid add the ValidationResult to the HttpContext Items.
public class ValidatorInterceptor : IValidatorInterceptor {
    public ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result) {
        if(!result.IsValid) {
            controllerContext.HttpContext.Items.Add("ValidationResult", result);
        }
        return result;
    }

    public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext) {
        return validationContext;
    }
}

// Check the HttpContext Items for the ValidationResult and return.
// a custom 400 error if it is found
public class ValidationResultAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext ctx) {
        if(!ctx.HttpContext.Items.TryGetValue("ValidationResult", out var value)) {
            return;
        }
        if(!(value is ValidationResult vldResult)) {
            return;
        }
        var model = vldResult.Errors.Select(err => new ValidationErrorModel(err)).ToArray();
        ctx.Result = new BadRequestObjectResult(model);
    }
}

// The custom error model now with 'ErrorCode'
public class ValidationErrorModel {
     public string PropertyName { get; }
     public string ErrorMessage { get; }
     public object AttemptedValue { get; }
     public string ErrorCode { get; }

     public ValidationErrorModel(ValidationFailure error) {
         PropertyName = error.PropertyName;
         ErrorMessage = error.ErrorMessage; 
         AttemptedValue = error.AttemptedValue; 
         ErrorCode =  error.ErrorCode;
     }
}

Then in Startup.cs you can register the ValidatorInterceptor and ValidationResultAttribute like so:

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
        services.AddMvc(o => {
            o.Filters.Add<ValidateModelAttribute>()
        });
    }
}

Refer this link for answer: https://github.com/JeremySkinner/FluentValidation/issues/548

Solution:

What I've done is that I created a basevalidator class which inherited both IValidatorInterceptor and AbstractValidator. In afterMvcvalidation method if validation is not successful, I map the error from validationResult to my custom response object and throw Custom exception which I catch in my exception handling middleware and return response.

On Serialization issue where controller gets null object:

modelstate.IsValid will be set to false when Json Deserialization fails during model binding and Error details will be stored in ModelState. [Which is what happened in my case]

Also due to this failure, Deserialization does not continue further and gets null object in controller method.

As of now, I have created a hack by setting serialization errorcontext.Handled = true manually and allowing my fluentvalidation to catch the invalid input.

https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [defined OnErrorAttribute in my request model].

I am searching for a better solution but for now this hack is doing the job.


try with this:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});

I validate the model with fluentvalidation, after build the BadResquest response in a ActionFilter class:

public class ValidateModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
                    .SelectMany(v => v.Errors)
                    .Select(v => v.ErrorMessage)
                    .ToList();

            var responseObj = new
            {
                Message = "Bad Request",
                Errors = errors                    
            };

            context.Result = new JsonResult(responseObj)
            {
                StatusCode = 400
            };
        }
    }
}

In StartUp.cs:

        services.AddMvc(options =>
        {
            options.Filters.Add(typeof(ValidateModelStateAttribute));
        })
        .AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>());

        services.Configure<ApiBehaviorOptions>(options =>
        {
            options.SuppressModelStateInvalidFilter = true;
        });

And it works fine. I hope you find it useful