您好我正在尝试使用.NET Core为我的webApi获取自定义验证响应。
这里我希望有像
这样的响应模型[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
我有一个验证器类,目前我们只检查ModalState.IsValid验证错误并将modelstate对象作为BadRequest传递。
但新要求要求我们为每个验证失败提供ErrorCodes。
我的示例Validator Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
我可以在我的行动中使用类似的东西来获得验证结果
Opt1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
并将ValidationResult操作到我的自定义Response对象。
或
Opt2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
但对于Opt2,我不知道如何从拦截器到控制器动作检索ValidationResult。
我想要的只是编写一个通用方法,以避免在每个控制器操作方法中调用Opt1进行验证。
请求指出我正确的资源。
答案 0 :(得分:4)
尝试一下:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
在ActionFilter类中构建BadResquest响应后,我用fluentvalidation验证了模型:
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;
});
它工作正常。我希望您觉得它有用
答案 1 :(得分:4)
对于我来说,最好在ASP.NET Core项目中使用以下代码
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
});
答案 2 :(得分:3)
在.net核心中,您可以组合使用IValidatorInterceptor将ValidationResult
复制到HttpContext.Items
,然后再复制ActionFilterAttribute
来检查结果并返回自定义响应(如果是)找到。
// 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>()
});
}
}
答案 3 :(得分:1)
请参阅此链接以获取答案:https://github.com/JeremySkinner/FluentValidation/issues/548
解决方案:
我所做的是创建了一个basevalidator类,它继承了IValidatorInterceptor和AbstractValidator。在afterMvcvalidation方法中,如果验证不成功,我将来自validationResult的错误映射到我的自定义响应对象并抛出自定义异常。
我在处理中间件和返回响应的异常中捕获。
在控制器获取空对象的序列化问题上:
我看到当模型绑定期间Json反序列化失败时,modelstate.IsValid将设置为false,错误详细信息将存储在ModelState中。 [这是发生在我身上的事情]
同样由于此失败,反序列化停止进一步继续并在控制器方法中获取null对象。
目前我通过手动设置序列化errorcontext.Handled = true并允许我的fluentvalidation捕获无效输入来给予hack。
https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [在我的请求模型中定义了OnErrorAttribute]。
我正在寻找更好的解决方案但是现在这个黑客正在做这个工作..
答案 4 :(得分:0)
类似于上面亚历山大的回答,我使用在源代码中找到的原始工厂创建了一个匿名对象,但只是更改了部分以返回自定义 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;
};
});