必需的数字参数在 JSON 中不包含时默认为 0

Required number parameter defaulting to 0 when not included in JSON

我有一个模型,我在其中使用 DataAnnotations 执行验证,例如

public class OrderDTO
{
    [Required]
    public int Id { get; set; }

    [Required]
    public Decimal Amount { get; set; }
}

然后我在每个请求中检查 ModelState 以确保 JSON 有效。

但是,我遇到了数字属性问题,例如上面的 Amount。即使它被设置为 [Required],如果它不包含在 JSON 中,它将跳过 ModelState 验证,因为它自动默认为 0 而不是 null,因此模型看起来有效尽管不是。

实现 'fix' 的一种简单方法是将所有数字属性设置为可为空(int?Decimal?)。如果我这样做,默认为 0 不会发生,但我不喜欢将此作为最终解决方案,因为我需要更改我的模型。

如果属性不属于 JSON,是否可以将属性设置为 null

因为 Decimal 是 non-nullable 类型所以你不能那样做。 您需要 Decimal? 来绑定 null 值。

您必须使用可空类型。正如您所知,由于 non-nullable 值不能为空,因此它将使用 0 作为默认值,因此看起来有一个值并始终通过验证。

正如您所说,它必须为 null 才能进行验证,因此可以为 null。另一种选择是编写您自己的验证属性,但这可能会导致问题,因为您很可能会说 if is null or 0 then not valid,当您希望将 0 作为可接受的值时,这是一个大问题,因为您随后需要另一种决定 0 何时有效和无效的方法。

自定义验证示例,并非特定于此案例。 Web API custom validation to check string against list of approved values

另一个选项可能是添加另一个 属性,它可以为空并为 non-nullable 属性 提供值。同样,这可能会导致 0 值出现问题。这是一个带有 Id 属性 的示例,您的 json 现在需要发送 NullableId 而不是 Id。

public class OrderDTO
{
    //Nullable property for json and validation 
    [Required]
    public int? NullableId {
        get {
            return Id == 0 ? null : Id; //This will always return null if Id is 0, this can be a problem
        }
        set {
            Id = value ?? 0; //This means Id is 0 when this is null, another problem
        }
    }

    //This can be used as before at any level between API and the database
    public int Id { get; set; }
}

如您所说,另一种选择是通过整个堆栈将模型更改为可为 null 的值。

最后,您可以考虑让一个外部模型进入 api 并具有可为 null 的属性,然后将其映射到当前模型,手动或使用类似 AutoMapper 的方法。

我同意其他人的观点,即 Decimal 是一种非 Nullable 类型,不能分配 null 值。此外,Required 属性仅检查 null、空字符串和空格。因此,对于您的特定要求,您可以使用 CustomValidationAttribute 并且可以创建自定义验证类型来对 Decimal 属性进行“0”检查。

intDecimal 不可能是 null。这就是创建可空值的原因。

您有多种选择[编辑:我刚刚意识到您要具体要求 Web-API,在这种情况下,我相信自定义活页夹选项会比我发布的代码更复杂。]:

  • 使 DTO 中的字段可为空
  • 创建一个具有可空类型的 ViewModel,在视图模型上添加所需的验证属性并将此 ViewModel 映射到您的 DTO(可能使用 automapper 或类似的东西)。
  • 手动验证请求(糟糕且容易出错的事情)

    public ActionResult MyAction(OrderDTO order)
    {
       // Validate your fields against your possible sources (Request.Form,QueryString, etc)
       if(HttpContext.Current.Request.Form["Ammount"] == null)
       {
           throw new YourCustomExceptionForValidationErrors("Ammount was not sent");
       }
    
       // Do your stuff
    }
    

  • 创建一个 custom binder 并在那里进行验证:

    public class OrderModelBinder : DefaultModelBinder 
    {
       protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext,
    PropertyDescriptor propertyDescriptor, object value)
       {
           if ((propertyDescriptor.PropertyType == typeof(DateTime) && value == null) ||
              (propertyDescriptor.PropertyType == typeof(int) && value == null) ||
              (propertyDescriptor.PropertyType == typeof(decimal) && value == null) ||
              (propertyDescriptor.PropertyType == typeof(bool) && value == null))
           {
               var modelName = string.IsNullOrEmpty(bindingContext.ModelName) ? "" : bindingContext.ModelName + ".";
               var name = modelName + propertyDescriptor.Name;
               bindingContext.ModelState.AddModelError(name, General.RequiredField);
            }
    
        return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value);
        }
    }
    

    并使用以下答案中描述的技术之一将您的活页夹注册到您的模型: 例如:

    [ModelBinder(typeof(OrderBinder))]
    public class OrderDTO
    {
        [Required]
        public int Id { get; set; }
    
        [Required]
        public Decimal Amount { get; set; }
    }