通过流利的 API 验证视图模型是否仅在 api 中传递一次

Validate if view model is only being passed once in an api through fluent API

我正在使用 Fluent API 来验证我 API 中的负载。 Street Address/MailingAddress/Lockbox Address 是域模型中的三个不同属性。我想在我的验证中确保街道地址和邮寄地址只传递一次。

正确的有效载荷

{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"MailingAddress": {
"city":"NewYork",
"address":"q2453"
},
"LockBoxAddress": {
"city":"Miami",
"address":"q23888"
}
}

领域模型

public string id{get;set;}
public string name{get;set;}
public Address streetAddress{get;set;}
public Address MailingAddress{get;set;}
public Address LockboxAddress{get;set;}

不正确的负载

{
"id" :123,
"name":"test",
"streetAddress": {
"city":"London",
"address":"q23"
},
"streetAddress": {
"city":"NewYork",
"address":"q2453"
}
}

我希望上面的有效负载出错,说你不能传递多个街道地址,我正在使用 Fluent API

流利API

RuleFor(x => x.streetAddress).Count(x =>x < 2).When(x => x.streetAddress!= null); 

没有 属性 来获取模型的计数。有什么想法吗?

  1. 您无法通过 Fluent 验证 API。因为只有一个streetAddress属性在你的域模型有效负载反序列化之后。另一方面,您的代码 RuleFor(x => x.streetAddress) returns IRuleBuilderInitial<XModel, Address> 而不是 IRuleBuilderInitial<XModel, IList<Address>>。为了实现你的目标,你应该确保你的验证发生在之前 FluentValidation,即反序列化时验证有效负载
  2. 有时客户端可能会发送带有重复密钥的 json,如果您使用 ASP.NET Core 2.1ASP.NET 2.2,默认情况下 不会失败

如何解决

要拒绝此类负载,请确保您的 Newtonsoft.Json 版本为 12.0.1 或更高版本 。如果您不确定,只需在 *.csproj:

中添加这样的参考
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />

然后创建自定义模型绑定器来处理重复键:

public class RejectDuplicatedKeysModelBinder : IModelBinder
{
    private JsonLoadSettings _loadSettings  = new JsonLoadSettings(){ DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error };

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
        var modelName = bindingContext.BinderModelName ?? bindingContext.OriginalModelName ?? bindingContext.FieldName ?? String.Empty;
        var modelType = bindingContext.ModelType;

        var req = bindingContext.HttpContext.Request;
        var raw = req.Body;
        if (raw == null) {
            bindingContext.ModelState.AddModelError(modelName, "invalid request body stream");
            return Task.CompletedTask;
        }
        JsonTextReader reader = new JsonTextReader(new StreamReader(raw));
        try {
            var json = (JObject)JToken.Load(reader, this._loadSettings);
            var o = json.ToObject(modelType);
            bindingContext.Result = ModelBindingResult.Success(o);
        }
        catch (JsonReaderException e) {
            var msg = $"wrong property with key='{e.Path}': {e.Message}";
            bindingContext.ModelState.AddModelError(modelName, msg); 
            bindingContext.Result = ModelBindingResult.Failed();
        } 
        catch(Exception e) {
            bindingContext.ModelState.AddModelError(modelName, e.ToString()); // you might want to custom the error info
            bindingContext.Result = ModelBindingResult.Failed();
        }
        return Task.CompletedTask;
    }
}

通过这种方式,重复的错误信息将被添加到ModelState

测试用例

让我们创建一个用于测试的操作方法:

[HttpPost]
public IActionResult Test([ModelBinder(typeof(RejectDuplicatedKeysModelBinder))]XModel model){
    if(! this.ModelState.IsValid){
        var problemDetails = new ValidationProblemDetails(this.ModelState)
        {
            Status = StatusCodes.Status400BadRequest,
        };
        return BadRequest(problemDetails);
    }
    return new JsonResult(model);
}

当 json 有重复的键时,我们会得到如下错误: