MVC 5:验证作为单独字段输入的两个 DateTime 属性 - 日、月、年

MVC 5: Validation of two DateTime properties entered as separate fields - day, month, year

顶级目标:我需要通过输入 2 个日期 - 出生日期和死亡日期来计算一个人的年龄。

目标详细信息:这些日期应作为单独的字段输入 - 日、月、年(如图所示 - The way I enter two DateTime fields)。 应验证这些字段的范围(日 - 1..31,月 - 1..12,年 - 1900..当前)和关系 - 死亡日期应大于出生日期。

问题:如果我不为 DateOfDeath3 字段创建 EditorFor(Date3GreaterThanAnother 验证属性应用于此字段),如何验证 2 个日期的关系并显示错误消息?我需要验证日期,但我只显示它的部分 - 日、月、年。

也许我选择了错误的方法来做这一切,请给我一个提示,告诉我如何才能做得更好...

谢谢!

当前实现细节:我已经实现了日期输入、保存、验证(仅限服务器端)。

为了将 DateTime 输入作为单独的字段,我使用以下 class:

public class Date3
{
    /// <summary>
    /// Creates Date3 instance with internal DateTime object set to null.
    /// </summary>
    public Date3()
    {
        DateTime = null;
    }

    public Date3(DateTime dateTime)
    {
        DateTime = dateTime;

        Day = dateTime.Day;
        Month = dateTime.Month;
        Year = dateTime.Year;
    }

    public DateTime? DateTime { get; private set; }

    /// <summary>
    /// Recreates inner DateTime object with specified Day, Month and Year properties.
    /// </summary>
    public void UpdateInner()
    {
        if (Day.HasValue && Month.HasValue && Year.HasValue)
            DateTime = new DateTime(Year.Value, Month.Value, Day.Value);
    }

    public override string ToString()
    {
        return DateTime.ToString();
    }

    [Range(1, 31, ErrorMessage = "Day number should be from 1 to 31.")]
    public int? Day { get; set; }

    [Range(1, 12, ErrorMessage = "Month number should be from 1 to 12.")]
    public int? Month { get; set; }

    [RangeCurrentYear(1900, ErrorMessage = "Year number should be from 1900 to current year.")]
    public int? Year { get; set; }
}

DateTime 的 "wrapper" 用于具有以下代码的视图模型中:

public class ViewModel
{
    ...

    public Date3 DateOfBirth3 { get; set; }

    [Date3GreaterThanAnother("DateOfBirth3", "Date of death should be after date of birth.")]
    public Date3 DateOfDeath3 { get; set; }

    ...
}

Date3GreaterThanAnother 验证属性 class 看起来像这样(我尝试实现客户端验证,这就是它实现 IClientValidatable 接口的原因):

[AttributeUsage(AttributeTargets.Property)]
public class Date3GreaterThanAnotherAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string _anotherProperty;
    public Date3GreaterThanAnotherAttribute(string anotherDatePropertyName, string errorMessage = null)
    {
        _anotherProperty = anotherDatePropertyName;
        if(errorMessage != null)
            ErrorMessage = errorMessage;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_anotherProperty);
        if (property == null)
            return new ValidationResult($"Unknown property {_anotherProperty}", new string[] {_anotherProperty});

        var otherDateValue = property.GetValue(validationContext.ObjectInstance, null);
        if (!(otherDateValue is Date3 otherDate3))
            return new ValidationResult($"Other property is not of Date3 type.");

        var date3 = (Date3)value;

        otherDate3.UpdateInner();
        date3.UpdateInner();

        if (otherDate3.DateTime >= date3.DateTime)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));

        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "greaterthananotherdate"
        };
        rule.ValidationParameters.Add("otherdate", _anotherProperty);

        yield return rule;
    }
}

最后,这是我在查看页面中显示它的方式(删除了不相关的标记代码):

...
    @Html.LabelFor(x => x.Estate.DateOfBirth3.Day, "Date of Birth", new {@class = "control-label col-md-2 control-label-details"})
    <div class="col-md-3">
        @Html.EditorFor(x => x.Estate.DateOfBirth3.Day, new {htmlAttributes = new {@class = "form-control date3 day", placeholder = "DD"}})
        @Html.ValidationMessageFor(x => x.Estate.DateOfBirth3.Day)
        /
        @Html.EditorFor(x => x.Estate.DateOfBirth3.Month, new {htmlAttributes = new {@class = "form-control date3 month", placeholder = "MM"}})
        @Html.ValidationMessageFor(x => x.Estate.DateOfBirth3.Month)
        /
        @Html.EditorFor(x => x.Estate.DateOfBirth3.Year, new {htmlAttributes = new {@class = "form-control date3 year", placeholder = "YYYY"}})
        @Html.ValidationMessageFor(x => x.Estate.DateOfBirth3.Year)
    </div>
...
    @Html.LabelFor(x => x.Estate.DateOfDeath3.Day, "Date of Death", new { @class = "control-label col-md-2 control-label-details" })
    <div class="col-md-3">
        @Html.EditorFor(x => x.Estate.DateOfDeath3.Day, new { htmlAttributes = new { @class = "form-control date3 day", placeholder = "DD" } })
        @Html.ValidationMessageFor(x => x.Estate.DateOfDeath3.Day)
        /
        @Html.EditorFor(x => x.Estate.DateOfDeath3.Month, new { htmlAttributes = new { @class = "form-control date3 month", placeholder = "MM" } })
        @Html.ValidationMessageFor(x => x.Estate.DateOfDeath3.Month)
        /
        @Html.EditorFor(x => x.Estate.DateOfDeath3.Year, new { htmlAttributes = new { @class = "form-control date3 year", placeholder = "YYYY" } })
        @Html.ValidationMessageFor(x => x.Estate.DateOfDeath3.Year)
    </div>
    @Html.ValidationMessageFor(x => x.Estate.DateOfDeath3)
...

如果 JS 代码是必需的 - 我在 Whosebug 上找到了关于不显眼 JQuery 验证的解决方案,但没有实现它,因为我无法实现它 运行 因为我的 MVC 相关问题.

在查看页面中不使用任何 DateOfDeath3 不引人注目的验证不起作用。 我尝试为 DateOfDeath3 添加 HiddenFor,不显眼的 jquery 验证开始工作但在 Date3GreaterThanAnother class 中我捕获了 value 参数 = null.[=16= 的异常]

$.validator.addMethod("greaterthananotherdate",
    function (value, element, params) {
        // TODO: implement validation logic.
        console.log("validation method triggered");
        return false;
    }, 'ERROR');


$.validator.unobtrusive.adapters.add(
    "greaterthananotherdate",
    ["otherdate"],
    function (options) {
        console.log("adapter triggered");
        options.rules["greaterthananotherdate"] = "#" + options.params.otherdate;
        options.messages["greaterthananotherdate"] = options.message;
    });

考虑以下方法:

这是您的模型:

public class AgeModel: IValidatableObject
{
    [DataType(DataType.Date)]
    public DateTime Birth { get; set; }

    [DataType(DataType.Date)]
    public DateTime Death { get; set; }

    public TimeSpan Age
    {
        get
        {
            return this.Death.Subtract(this.Birth);
        }
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (this.Death < this.Birth)
            yield return new ValidationResult("It died before birth", new[] { nameof(this.Death) });

        if (this.Birth > this.Death)
            yield return new ValidationResult("It born after death", new[] { nameof(this.Birth) });
    }
}

这是您的看法:

@using (Html.BeginForm("Age", "User", FormMethod.Post))
{
    <br />
    @Html.EditorFor(m => m.Birth)
    @Html.ValidationMessageFor(m => m.Birth)
    <br />
    @Html.EditorFor(m => m.Death)
    @Html.ValidationMessageFor(m => m.Death)
    <br />
    @Html.ValidationSummary();
    <br />
    <input type="submit" value="Submit" />
}

不需要对 DateTime 进行包装,所有控件都自动呈现