如何在 ASP.NET Core 中创建自定义验证器,它也会因无效输入而触发?
How to create a custom validator in ASP.NET Core that fires for invalid input too?
我在 ASP.NET Core 3.1 中为 DateTime
字段创建了自定义验证器,如下所示:
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//… some logic
}
}
但是,我的问题是此自定义验证器仅在我将日期值放入文本框控件时触发。它不会触发无效输入,例如当我在文本框中输入字符串 'aaa' 时。
我的问题是如何让这个自定义验证器即使对于 'string' 等无效输入也能触发
原因是我想让这个自定义验证器替换 [Required]
、[ReqularExpression]
等。一种 'One ring (validator) to rule them all'。我怎样才能做到这一点?
TL;DR: 当您提交无法转换为 DateTime
的值时,模型绑定失败。由于已经存在与 属性 关联的验证错误,因此不会触发后续验证(包括您的 CustomDate
验证器)。您的 属性 仍在接受验证,但是:如果您输入值 aaa
,ModelState.IsValid
将 return false
.
您最初发布的代码 应该 可以正常工作——但我怀疑它并没有像您 期望的 那样工作.最值得注意的是,您的困惑可能源于以下陈述:
"…this custom validator fires only when I put a date value in the text box control."
那是也真的!让我来完成整个过程。
原码
为了帮助说明这一点,我希望你不介意我恢复你的原始代码示例,因为有具体的参考资料很有用。
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : Attribute, IModelValidator
{
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
if (Convert.ToDateTime(context.Model) > DateTime.Now)
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - future date")
};
else if (Convert.ToDateTime(context.Model) < new DateTime(1970, 1, 1))
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - date is less than 1970 year")
};
else
return Enumerable.Empty<ModelValidationResult>();
}
}
验证过程
在我完成整个过程之前,有四个重要的基本注意事项需要注意:
- 模型绑定发生在之前模型验证。
- 如果绑定失败,模型绑定将引发其自身的验证错误。
- 验证属性仅对保留
IsValid
. 的属性进行评估
ModelValidationContext.Model
属性 被输入到经过验证的 属性——因此,在这种情况下,DateTime
值。
用例 #1:无效值
考虑到这些因素,下面是当您提交一个值时发生的情况,例如aaa
在映射到您验证的字段中 DOB
属性:
- 模型绑定器尝试将值
aaa
绑定到 DateTime
属性。
- 模型绑定器失败,将
ModelError
添加到您的 ModelStateDictionary
。
- 您的
CustomDate
验证器从未触发,因为该字段已经验证失败。
用例 #2:缺失值
看看另一个测试用例很有启发性。不要输入 aaa
,只要根本不输入任何值即可。在这种情况下,过程看起来有点不同:
- 模型绑定器找不到您的
DateTime
属性 的值,因此不会发生绑定。
- 您模型的 属性 初始化为
DateTime
的默认值 0001-01-01 00:00:00
。
- 你的
CustomDate
验证器触发,添加一个 ModelError
因为 "Invalid - date is less than 1970 year".
分析
正如您在上面看到的,true 当提交虚假日期时,您的 CustomDate
验证器没有触发。但是 并不 意味着没有进行验证。相反,验证已经已经发生并且失败了。如果您输入有效日期——或者根本不输入日期——则不会发生模型绑定错误,并且您的 CustomDate
验证器将按预期执行。
重温您的问题
"How to make this custom validator to fire even for invalid inputs like 'string' etc."
最终,我没有回答那个问题。但我认为我的回答会解释为什么会发生这种情况,以及为什么你的输入仍然得到验证那。请记住,即使你的 CustomDate
验证器 did 触发了,它的行为就像你根本没有提交值一样,因为 context.Model
值将默认为 0001-01-01 00:00:00
。主要区别在于您不会收到相同的错误消息,因为错误来自不同的来源。
强制验证
我不推荐这个,但是如果你真的想要你的 CustomDate
验证器触发,你可以将它应用到 string
属性 代替。在这种情况下,模型绑定不会失败,您的 CustomDate
验证器仍会被调用。如果你追求这个,你需要进行额外的验证以确保日期格式正确(例如,通过抢占或处理 InvalidFormatException
s)。但是,当然,您的日期将存储为 string
,这可能不是您想要的。
代码建议
这有点超出您最初问题的范围,但我在这里推荐以下内容:
- 您不需要在验证器中执行
Convert.ToDateTime()
;您的 context.Model
字段 已经 一个 DateTime
。您只需要将其转换回 DateTime
对象(例如 (DateTime)context.Model
),以便您的代码知道这一点。
- 至少,您应该考虑使用
<input type="date" />
(reference),在大多数浏览器上,它会限制输入正确的日期,同时还提供基本的日期选择器。
- 或者,如果您需要对演示文稿和客户端验证进行更多控制,可以考虑使用 JavaScript 编写的许多更复杂的 date/time 控件。
我在 ASP.NET Core 3.1 中为 DateTime
字段创建了自定义验证器,如下所示:
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//… some logic
}
}
但是,我的问题是此自定义验证器仅在我将日期值放入文本框控件时触发。它不会触发无效输入,例如当我在文本框中输入字符串 'aaa' 时。
我的问题是如何让这个自定义验证器即使对于 'string' 等无效输入也能触发
原因是我想让这个自定义验证器替换 [Required]
、[ReqularExpression]
等。一种 'One ring (validator) to rule them all'。我怎样才能做到这一点?
TL;DR: 当您提交无法转换为 DateTime
的值时,模型绑定失败。由于已经存在与 属性 关联的验证错误,因此不会触发后续验证(包括您的 CustomDate
验证器)。您的 属性 仍在接受验证,但是:如果您输入值 aaa
,ModelState.IsValid
将 return false
.
您最初发布的代码 应该 可以正常工作——但我怀疑它并没有像您 期望的 那样工作.最值得注意的是,您的困惑可能源于以下陈述:
"…this custom validator fires only when I put a date value in the text box control."
那是也真的!让我来完成整个过程。
原码
为了帮助说明这一点,我希望你不介意我恢复你的原始代码示例,因为有具体的参考资料很有用。
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : Attribute, IModelValidator
{
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
if (Convert.ToDateTime(context.Model) > DateTime.Now)
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - future date")
};
else if (Convert.ToDateTime(context.Model) < new DateTime(1970, 1, 1))
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - date is less than 1970 year")
};
else
return Enumerable.Empty<ModelValidationResult>();
}
}
验证过程
在我完成整个过程之前,有四个重要的基本注意事项需要注意:
- 模型绑定发生在之前模型验证。
- 如果绑定失败,模型绑定将引发其自身的验证错误。
- 验证属性仅对保留
IsValid
. 的属性进行评估
ModelValidationContext.Model
属性 被输入到经过验证的 属性——因此,在这种情况下,DateTime
值。
用例 #1:无效值
考虑到这些因素,下面是当您提交一个值时发生的情况,例如aaa
在映射到您验证的字段中 DOB
属性:
- 模型绑定器尝试将值
aaa
绑定到DateTime
属性。 - 模型绑定器失败,将
ModelError
添加到您的ModelStateDictionary
。 - 您的
CustomDate
验证器从未触发,因为该字段已经验证失败。
用例 #2:缺失值
看看另一个测试用例很有启发性。不要输入 aaa
,只要根本不输入任何值即可。在这种情况下,过程看起来有点不同:
- 模型绑定器找不到您的
DateTime
属性 的值,因此不会发生绑定。 - 您模型的 属性 初始化为
DateTime
的默认值0001-01-01 00:00:00
。 - 你的
CustomDate
验证器触发,添加一个ModelError
因为 "Invalid - date is less than 1970 year".
分析
正如您在上面看到的,true 当提交虚假日期时,您的 CustomDate
验证器没有触发。但是 并不 意味着没有进行验证。相反,验证已经已经发生并且失败了。如果您输入有效日期——或者根本不输入日期——则不会发生模型绑定错误,并且您的 CustomDate
验证器将按预期执行。
重温您的问题
"How to make this custom validator to fire even for invalid inputs like 'string' etc."
最终,我没有回答那个问题。但我认为我的回答会解释为什么会发生这种情况,以及为什么你的输入仍然得到验证那。请记住,即使你的 CustomDate
验证器 did 触发了,它的行为就像你根本没有提交值一样,因为 context.Model
值将默认为 0001-01-01 00:00:00
。主要区别在于您不会收到相同的错误消息,因为错误来自不同的来源。
强制验证
我不推荐这个,但是如果你真的想要你的 CustomDate
验证器触发,你可以将它应用到 string
属性 代替。在这种情况下,模型绑定不会失败,您的 CustomDate
验证器仍会被调用。如果你追求这个,你需要进行额外的验证以确保日期格式正确(例如,通过抢占或处理 InvalidFormatException
s)。但是,当然,您的日期将存储为 string
,这可能不是您想要的。
代码建议
这有点超出您最初问题的范围,但我在这里推荐以下内容:
- 您不需要在验证器中执行
Convert.ToDateTime()
;您的context.Model
字段 已经 一个DateTime
。您只需要将其转换回DateTime
对象(例如(DateTime)context.Model
),以便您的代码知道这一点。 - 至少,您应该考虑使用
<input type="date" />
(reference),在大多数浏览器上,它会限制输入正确的日期,同时还提供基本的日期选择器。 - 或者,如果您需要对演示文稿和客户端验证进行更多控制,可以考虑使用 JavaScript 编写的许多更复杂的 date/time 控件。