必需的数字参数在 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”检查。
int
或 Decimal
不可能是 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; }
}
我有一个模型,我在其中使用 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”检查。
int
或 Decimal
不可能是 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; } }