Use custom validation responses with fluent validation

前端 未结 4 1161
天命终不由人
天命终不由人 2020-12-29 12:46

Hello I am trying to get custom validation response for my webApi using .NET Core.

Here I want to have response model like

[{
  ErrorCode:
  ErrorFi         


        
相关标签:
4条回答
  • 2020-12-29 12:54

    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.

    0 讨论(0)
  • 2020-12-29 13:09

    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
          });
    
    0 讨论(0)
  • 2020-12-29 13:11

    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

    0 讨论(0)
  • 2020-12-29 13:15

    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>()
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题