使用自定义验证响应和流畅的验证

时间:2017-08-18 13:35:54

标签: c# .net asp.net-web-api asp.net-core fluentvalidation

您好我正在尝试使用.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进行验证。

请求指出我正确的资源。

5 个答案:

答案 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中,您可以像这样注册ValidatorInterceptorValidationResultAttribute

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;
                    };
                });