首页 > 解决方案 > 如何在 ASP.NET Core 中创建一个也会触发无效输入的自定义验证器?

问题描述

我为DateTimeASP.NET Core 3.1 中的字段创建了一个自定义验证器,如下所示:

[CustomDate]
public DateTime DOB { get; set; }

public class CustomDate : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        //… some logic
    }
}

但是,我的问题是这个自定义验证器只有在我将日期值放入文本框控件时才会触发。它不会因无效输入而触发,例如当我在文本框中输入字符串“aaa”时。

我的问题是如何使这个自定义验证器即使对于“字符串”等无效输入也能触发。

原因是我想让这个自定义验证器替换[Required],[ReqularExpression]等。一种“一个环(验证器)来统治它们”。我怎样才能做到这一点?

标签: asp.net-coremodel-bindingasp.net-core-3.1customvalidatormodel-validation

解决方案


TL;DR:当您提交无法转换为 的值时DateTime,模型绑定失败。由于已经存在与该属性关联的验证错误,因此后续验证(包括您的CustomDate验证器)不会触发。但是,您的属性仍在验证中:如果您输入 的值aaaModelState.IsValid将返回false


您最初发布的代码应该可以正常工作 - 但我怀疑它没有按照您期望的方式工作。最值得注意的是,您的困惑可能源于以下陈述:

“……这个自定义验证器仅在我将日期值放入文本框控件时触发。”

也是真的!让我来看看整个过程。

原始代码

为了帮助说明这一点,我希望您不要介意我恢复您的原始代码示例,因为对工作进行具体参考很有用。

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

验证过程

在我完成这个过程之前,这里有四个重要的基本考虑因素需要注意:

  1. 模型绑定发生模型验证之前。
  2. 如果绑定失败,模型绑定将引发自己的验证错误。
  3. 验证属性仅对剩余的属性进行评估IsValid
  4. ModelValidationContext.Model属性被键入到已验证的属性中——因此,在这种情况下,是一个DateTime值。

用例 #1:无效值

aaa考虑到这些考虑,当您在映射到已验证DOB属性的字段中提交 eg 值时,会发生以下情况:

  1. 模型绑定器尝试将 的值绑定aaaDateTime属性。
  2. 模型绑定器失败,将 a 添加ModelError到您的ModelStateDictionary.
  3. 您的CustomDate验证器永远不会触发,因为该字段已经失败验证

用例 #2:缺失值

查看另一个测试用例很有启发性。与其放入aaa,不如根本不放入值。在这种情况下,该过程看起来有点不同:

  1. DateTime模型绑定器没有为您的属性找到值,因此不会发生绑定。
  2. 您的模型的属性初始化为DateTime的默认值0001-01-01 00:00:00
  3. 您的CustomDate验证器触发,添加一个ModelError因为“无效 - 日期小于 1970 年”。

分析

正如您在上面看到的,当提交虚假日期时,您的验证器确实没有触发。CustomDate但这并不意味着验证不会发生。相反,验证已经发生并且失败了。如果您输入一个有效的日期——或者根本不输入一个日期——那么模型绑定错误将不会发生,并且您的CustomDate验证器将按预期执行。

重新审视你的问题

“如何使这个自定义验证器即使对于'字符串'等无效输入也能触发。”

最终,我没有回答这个问题。但我认为我的回答将解释为什么会发生这种情况,以及为什么您的输入仍然得到验证。请记住,即使您的CustomDate验证器确实触发了,它的行为也会与您根本没有提交值一样,因为该context.Model值会默认为0001-01-01 00:00:00. 主要区别在于您没有收到相同的错误消息,因为错误来自不同的来源。

强制验证

我不推荐这样做,但如果您真的希望CustomDate验证器触发,您可以将其应用于string属性。在这种情况下,模型绑定不会失败,您的CustomDate验证器仍然会被调用。如果您追求这一点,则需要进行额外的验证以确保日期格式正确(例如,通过抢占或处理InvalidFormatExceptions)。但是,当然,您的日期将存储为string,这可能不是您想要的。

代码建议

这有点超出您原始问题的范围,但是当我在这里时,我建议您这样做:

  1. 你不需要Convert.ToDateTime()在你的验证器中做 a;您的context.Model字段已经DateTime. 您只需要将它转换回一个DateTime对象(例如,(DateTime)context.Model),以便您的代码知道这一点。
  2. 至少,您应该考虑使用<input type="date" />( reference ),在大多数浏览器上,它会将输入限制为正确的日期,同时还提供基本的日期选择器。
  3. 或者,如果您需要对演示文稿和客户端验证进行更多控制,您可以考虑实施许多用 JavaScript 编写的更复杂的日期/时间控件。

推荐阅读