问题
Hello I am trying to get custom validation response for my webApi using .NET Core.
Here I want to have response model like
[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
I have a validator class and currently we just check ModalState.IsValid for validation Error and pass on the modelstate object as BadRequest.
But new requirement wants us to have ErrorCodes for each validation failure.
My sample Validator Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
I can use something similar in my actions to get validation result
Opt1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
and manipulate ValidationResult to my customn Response object.
or
Opt2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
but for Opt2 I don't know how to retrieve ValidationResult from interceptor to controller action.
All I want is to write a common method so that I avoid calling Opt1 in every controller action method for validation.
Request to point me to correct resource.
回答1:
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
回答2:
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
});
回答3:
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.
回答4:
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>()
});
}
}
来源:https://stackoverflow.com/questions/45758024/use-custom-validation-responses-with-fluent-validation