web-dev-qa-db-ja.com

流暢な検証でカスタム検証応答を使用する

こんにちは私は.NETCoreを使用してwebApiのカスタム検証応答を取得しようとしています。

ここで私は次のような応答モデルが欲しいです

[{
  ErrorCode:
  ErrorField:
  ErrorMsg:
}]

バリデータークラスがあり、現在、ModalState.IsValidで検証エラーをチェックし、modelstateオブジェクトをBadRequestとして渡します。

しかし、新しい要件では、検証の失敗ごとにErrorCodesが必要です。

私のサンプルバリデータークラス

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を呼び出さないように、共通のメソッドを作成するだけです。

正しいリソースを教えてください。

7
RajGan

回答については、このリンクを参照してください: https://github.com/JeremySkinner/FluentValidation/issues/548

解決策:

私が行ったことは、IValidatorInterceptorとAbstractValidatorの両方を継承するbasevalidatorクラスを作成することです。 afterMvcvalidationメソッドで、検証が成功しなかった場合、validationResultからのエラーをカスタム応答オブジェクトにマップし、カスタム例外をスローしました。

ミドルウェアを処理する例外をキャッチし、応答を返します。

コントローラーがnullオブジェクトを取得するシリアル化の問題について:

モデルのバインド中にJsonDeserializationが失敗すると、modelstate.IsValidがfalseに設定され、エラーの詳細がModelStateに保存されます。 [これが私に起こっていることです]

また、この失敗により、逆シリアル化は停止してさらに続行し、コントローラーメソッドでnullオブジェクトを取得します。

現在、シリアル化errorcontext.Handled = trueを手動で設定し、fluentvalidationが無効な入力をキャッチできるようにすることでハックを行っています。

https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [リクエストモデルで定義されたOnErrorAttribute]。

私はより良い解決策を探していますが、今のところこのハックは仕事をしています。

1
RajGan

私の場合、ASP.NETCoreプロジェクトで次のコードを使用することをお勧めします

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

これで試してください:

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

そしてそれはうまくいきます。お役に立てば幸いです

6
asd

.netコアでは、IValidatorInterceptorを組み合わせてValidationResultHttpContext.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.csValidatorInterceptorValidationResultAttributeを次のように登録できます。

public class Startup {
    public void ConfigureServices(IServiceCollection services) {
        services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
        services.AddMvc(o => {
            o.Filters.Add<ValidateModelAttribute>()
        });
    }
}
0
Peter Riesz