使用具有流畅验证的自定义验证响应
Use custom validation responses with fluent validation
您好,我正在尝试使用 .NET Core 为我的 webApi 获取自定义验证响应。
这里我想要像
这样的响应模型
[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
我有一个验证器 class,目前我们只是检查 ModalState.IsValid 验证错误并将模型状态对象作为 BadRequest 传递。
但是新要求要求我们为每次验证失败设置错误代码。
我的样本验证器Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
我可以在我的操作中使用类似的东西来获得验证结果
选项 1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
并将 ValidationResult 操作到我的自定义 Response 对象。
或者
选项 2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
但对于 Opt2,我不知道如何从拦截器检索 ValidationResult 到控制器操作。
我只想写一个通用方法,这样我就可以避免在每个控制器操作方法中调用 Opt1 进行验证。
请求指出我正确的资源。
参考此 link 以获得答案:https://github.com/JeremySkinner/FluentValidation/issues/548
解法:
我所做的是创建了一个 basevalidator class,它继承了 IValidatorInterceptor 和 AbstractValidator。在 afterMvcvalidation 方法中,如果验证不成功,我将错误从 validationResult 映射到我的自定义响应对象,并抛出我在异常处理中间件和 return 响应中捕获的自定义异常。
关于控制器获取空对象的序列化问题:
modelstate.IsValid 当 Json 反序列化在模型绑定过程中失败时将设置为 false,并将错误详细信息存储在 ModelState 中。 [这就是我的情况]
同样由于此故障,反序列化不会继续进行,并在控制器方法中获取空对象。
截至目前,我已经通过手动设置序列化 errorcontext.Handled = true 并允许我的 fluentvalidation 捕获无效输入来创建 hack。
https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [在我的请求模型中定义了 OnErrorAttribute]。
我正在寻找更好的解决方案,但目前这个黑客正在做这项工作。
试试这个:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
在 ActionFilter 中构建 BadResquest 响应后,我使用 fluentvalidation 验证模型 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
};
}
}
}
在StartUp.cs中:
services.AddMvc(options =>
{
options.Filters.Add(typeof(ValidateModelStateAttribute));
})
.AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>());
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
而且效果很好。我希望你觉得它有用
对于我来说,在ASP.NET核心项目
中使用下面的代码比较好
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
});
};
});
还要考虑到您可以使用具体类型代替匿名对象。例如,
new BadRequestObjectResult(new ValidationErrorViewModel
{
ErrorCode = "Your validation error code",
Message = errors
});
在 .net 核心中,您可以结合使用 IValidatorInterceptor 将 ValidationResult
复制到 HttpContext.Items
,然后使用 ActionFilterAttribute
检查结果和 return如果找到自定义响应。
// 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;
}
}
然后在 Startup.cs
你可以像这样注册 ValidatorInterceptor
和 ValidationResultAttribute
:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
services.AddMvc(o => {
o.Filters.Add<ValidateModelAttribute>()
});
}
}
与上面 Alexander 的回答类似,我使用我可以在源代码中找到的原始工厂创建了一个匿名对象,但只是更改了部分以返回自定义 HTTP 响应代码(在我的例子中是 422)。
ApiBehaviorOptionsSetup (Original factory)
services.AddMvcCore()
...
// other builder methods here
...
.ConfigureApiBehaviorOptions(options =>
{
// Replace the built-in ASP.NET InvalidModelStateResponse to use our custom response code
options.InvalidModelStateResponseFactory = context =>
{
var problemDetailsFactory = context.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState, statusCode: 422);
var result = new UnprocessableEntityObjectResult(problemDetails);
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
});
您好,我正在尝试使用 .NET Core 为我的 webApi 获取自定义验证响应。
这里我想要像
这样的响应模型[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
我有一个验证器 class,目前我们只是检查 ModalState.IsValid 验证错误并将模型状态对象作为 BadRequest 传递。
但是新要求要求我们为每次验证失败设置错误代码。
我的样本验证器Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
我可以在我的操作中使用类似的东西来获得验证结果
选项 1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
并将 ValidationResult 操作到我的自定义 Response 对象。
或者
选项 2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
但对于 Opt2,我不知道如何从拦截器检索 ValidationResult 到控制器操作。
我只想写一个通用方法,这样我就可以避免在每个控制器操作方法中调用 Opt1 进行验证。
请求指出我正确的资源。
参考此 link 以获得答案:https://github.com/JeremySkinner/FluentValidation/issues/548
解法:
我所做的是创建了一个 basevalidator class,它继承了 IValidatorInterceptor 和 AbstractValidator。在 afterMvcvalidation 方法中,如果验证不成功,我将错误从 validationResult 映射到我的自定义响应对象,并抛出我在异常处理中间件和 return 响应中捕获的自定义异常。
关于控制器获取空对象的序列化问题:
modelstate.IsValid 当 Json 反序列化在模型绑定过程中失败时将设置为 false,并将错误详细信息存储在 ModelState 中。 [这就是我的情况]
同样由于此故障,反序列化不会继续进行,并在控制器方法中获取空对象。
截至目前,我已经通过手动设置序列化 errorcontext.Handled = true 并允许我的 fluentvalidation 捕获无效输入来创建 hack。
https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [在我的请求模型中定义了 OnErrorAttribute]。
我正在寻找更好的解决方案,但目前这个黑客正在做这项工作。
试试这个:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
在 ActionFilter 中构建 BadResquest 响应后,我使用 fluentvalidation 验证模型 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
};
}
}
}
在StartUp.cs中:
services.AddMvc(options =>
{
options.Filters.Add(typeof(ValidateModelStateAttribute));
})
.AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>());
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
而且效果很好。我希望你觉得它有用
对于我来说,在ASP.NET核心项目
中使用下面的代码比较好 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
});
};
});
还要考虑到您可以使用具体类型代替匿名对象。例如,
new BadRequestObjectResult(new ValidationErrorViewModel
{
ErrorCode = "Your validation error code",
Message = errors
});
在 .net 核心中,您可以结合使用 IValidatorInterceptor 将 ValidationResult
复制到 HttpContext.Items
,然后使用 ActionFilterAttribute
检查结果和 return如果找到自定义响应。
// 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;
}
}
然后在 Startup.cs
你可以像这样注册 ValidatorInterceptor
和 ValidationResultAttribute
:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
services.AddMvc(o => {
o.Filters.Add<ValidateModelAttribute>()
});
}
}
与上面 Alexander 的回答类似,我使用我可以在源代码中找到的原始工厂创建了一个匿名对象,但只是更改了部分以返回自定义 HTTP 响应代码(在我的例子中是 422)。
ApiBehaviorOptionsSetup (Original factory)
services.AddMvcCore()
...
// other builder methods here
...
.ConfigureApiBehaviorOptions(options =>
{
// Replace the built-in ASP.NET InvalidModelStateResponse to use our custom response code
options.InvalidModelStateResponseFactory = context =>
{
var problemDetailsFactory = context.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState, statusCode: 422);
var result = new UnprocessableEntityObjectResult(problemDetails);
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
});