.NET不显眼的验证自定义日期范围属性显示错误的日期,除非应用程序池被回收。

问题描述 投票:1回答:1

我们有一个.NET MVC应用程序。在其中,我们有Unobtrusive Validation插件和Date Ranges的自定义验证器。

Imports System.ComponentModel.DataAnnotations

Public Class DateRangeAttribute
Inherits ValidationAttribute
Implements IClientValidatable, IMetadataAware

'[redacted for brevity]

Public Sub New(minDate As String, maxDate As String, minDateRelativeDays As Integer, maxDateRelativeDays As Integer)
    MyBase.New(DefaultErrorMessage)

    If minDate = String.Empty Then
        Me.MinDate = Today
    Else
        Me.MinDate = ParseDate(minDate)
    End If

    If maxDate = String.Empty Then
        Me.MaxDate = Today
    Else
        Me.MaxDate = ParseDate(maxDate)
    End If

    Me.MinDate = Me.MinDate.AddDays(minDateRelativeDays)
    Me.MaxDate = Me.MaxDate.AddDays(maxDateRelativeDays)

End Sub

'[redacted for brevity]

验证是通过在模型中的属性中添加适当的装饰来使用的。

<Display(name:="Completed date"), DateRange("2000/01/01", Nothing, 0, 0), DisplayFormat(DataFormatString:="{0:dd/MMM/yyyy}", ApplyFormatInEditMode:=True)>

在本例中,由于 "Nothing "被传递给了 maxDate 参数,它将被设置为 Today. 这很好用,它不会允许你输入比Today晚的日期,只是它似乎不会在一天结束时 "刷新",除非IIS App Pool被回收。当App Pool从一天一直运行到下一天时,日期范围验证失败,因为它认为 Today 还是昨天的事。这种情况发生在客户端和服务器端。我们不使用输出缓存,也不做任何事情来显式缓存验证器的结果。

它的行为就像它缓存了 Today 是,但这似乎完全错误。我们已经放入了一个变通方法,强制App Pool在午夜回收,这已经缓解了这个问题,但对我来说仍然没有意义。

.net vb.net iis unobtrusive-validation
1个回答
0
投票

我不知道这对我有多大帮助,因为我对VB.NET不是很熟悉,但我在一个ASP.NET Core应用程序中遇到了一个非常类似的问题,我认为我的解决方案背后的理论可能是相同的。

我发现属性似乎是在应用程序编译时被创建的,所以任何属性都是在那个时候被设置的,而且永远不会改变,除非另一个方法改变它们。在我的案例中,我有一个在构造函数中设置MinDate和MaxDate参数的验证属性,所以我看到了和你描述的同样的问题--minmax日期一开始是正确的,但是从来没有改变过,所以一天之后就不正确了。

我通过将MinDateMaxDate参数替换为BeforeSteps和AfterSteps参数,并有几个公共方法,用这些参数计算并返回最小和最大日期,解决了这个问题。


之前不能用的代码。

属性:

public class DateRangeFromTodayAttribute : ValidationAttribute
{
    private readonly DatePart _rangeType;
    public DateTime MinDate { get; set; } = DateTime.MinValue;
    public DateTime MaxDate { get; set; } = DateTime.MaxValue;
    public bool IsDateOnly { get; set; }

    public DateRangeFromTodayAttribute(DatePart rangeType, int steps, StepType stepType, bool isDateOnly = true)
    {
        _rangeType = rangeType;
        IsDateOnly = isDateOnly;

        if (stepType == StepType.BeforeToday)
        {
            MinDate = GetCalculatedDate(-steps);
        }

        if (stepType == StepType.AfterToday)
        {
            MaxDate = GetCalculatedDate(steps);
        }
    }

    public DateRangeFromTodayAttribute(DatePart rangeType, int beforeToday, int afterToday, bool isDateOnly = true)
    {
        _rangeType = rangeType;
        IsDateOnly = isDateOnly;
        MinDate = GetCalculatedDate(-beforeToday);
        MaxDate = GetCalculatedDate(afterToday);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null && !(value is DateTime))
        {
            throw new InvalidOperationException($"{nameof(DateRangeFromTodayAttribute)} can only be used on DateTime properties.");
        }

        if (!(value is DateTime date))
        {
            return ValidationResult.Success;
        }

        var minIsValid = date >= MinDate;
        var maxIsValid = date <= MaxDate;

        return minIsValid && maxIsValid
            ? ValidationResult.Success
            : new ValidationResult(ErrorMessage);
    }

    private DateTime GetCalculatedDate(int steps)
    {
        DateTime now = IsDateOnly ? DateTime.Today : DateTime.Now;
        return _rangeType switch
        {
            DatePart.Ticks => now.AddTicks(steps),
            DatePart.Milliseconds => now.AddMilliseconds(steps),
            DatePart.Seconds => now.AddSeconds(steps),
            DatePart.Minutes => now.AddMinutes(steps),
            DatePart.Hours => now.AddHours(steps),
            DatePart.Days => now.AddDays(steps),
            DatePart.Months => now.AddMonths(steps),
            DatePart.Years => now.AddYears(steps),
            _ => throw new ArgumentOutOfRangeException()
        };
    }
}

public enum DatePart
{
    Ticks,
    Milliseconds,
    Seconds,
    Minutes,
    Hours,
    Days,
    Months,
    Years,
}

public enum StepType
{
    BeforeToday,
    AfterToday,
}

适配器。

public class DateRangeFromTodayAttributeAdapter : AttributeAdapterBase<DateRangeFromTodayAttribute>
{
    public DateRangeFromTodayAttributeAdapter(DateRangeFromTodayAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
    {
    }

    public override void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-daterangefromtoday", GetErrorMessage(context));
        MergeAttribute(context.Attributes, "min", Attribute.IsDateOnly ? Attribute.MinDate.Date.ToString("yyyy-MM-dd") : Attribute.MinDate.ToString("yyyy-MM-dd"));
        MergeAttribute(context.Attributes, "max", Attribute.IsDateOnly ? Attribute.MaxDate.Date.ToString("yyyy-MM-dd") : Attribute.MaxDate.ToString("yyyy-MM-dd"));
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
        return Attribute.ErrorMessage;
    }
}

工作代码

这是我的新属性和适配器代码--看看日期如何不再在构造函数中。

Attribute:

public class DateRangeFromTodayAttribute : ValidationAttribute
{
    public DatePart RangeType { get; set; }
    public int? StepsBefore { get; set; }
    public int? StepsAfter { get; set; }
    public bool IsDateOnly { get; set; }

    public DateRangeFromTodayAttribute(DatePart rangeType, int steps, StepType stepType, bool isDateOnly = true)
    {
        RangeType = rangeType;
        IsDateOnly = isDateOnly;

        if (stepType == StepType.BeforeToday)
        {
            StepsBefore = steps;
        }

        if (stepType == StepType.AfterToday)
        {
            StepsAfter = steps;
        }
    }

    public DateRangeFromTodayAttribute(DatePart rangeType, int beforeToday, int afterToday, bool isDateOnly = true)
    {
        RangeType = rangeType;
        IsDateOnly = isDateOnly;
        StepsBefore = beforeToday;
        StepsAfter = afterToday;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null && !(value is DateTime))
        {
            throw new InvalidOperationException($"{nameof(DateRangeFromTodayAttribute)} can only be used on DateTime properties.");
        }

        if (!(value is DateTime date))
        {
            return ValidationResult.Success;
        }

        DateTime minDate = GetMinDate();
        bool minIsValid = date >= minDate;

        DateTime maxDate = GetMaxDate();
        bool maxIsValid = date <= maxDate;

        return minIsValid && maxIsValid
            ? ValidationResult.Success
            : new ValidationResult(ErrorMessage);
    }

    public DateTime GetMinDate()
    {
        return StepsBefore is null
            ? DateTime.MinValue
            : GetCalculatedDate(-StepsBefore.Value);
    }

    public DateTime GetMaxDate()
    {
        return StepsAfter is null
            ? DateTime.MaxValue
            : GetCalculatedDate(StepsAfter.Value);
    }

    private DateTime GetCalculatedDate(int steps)
    {
        DateTime now = IsDateOnly ? DateTime.Today : DateTime.Now;
        return RangeType switch
        {
            DatePart.Ticks => now.AddTicks(steps),
            DatePart.Milliseconds => now.AddMilliseconds(steps),
            DatePart.Seconds => now.AddSeconds(steps),
            DatePart.Minutes => now.AddMinutes(steps),
            DatePart.Hours => now.AddHours(steps),
            DatePart.Days => now.AddDays(steps),
            DatePart.Months => now.AddMonths(steps),
            DatePart.Years => now.AddYears(steps),
            _ => throw new ArgumentOutOfRangeException()
        };
    }
}

public enum DatePart
{
    Ticks,
    Milliseconds,
    Seconds,
    Minutes,
    Hours,
    Days,
    Months,
    Years,
}

public enum StepType
{
    BeforeToday,
    AfterToday,
}

适配器。

public class DateRangeFromTodayAttributeAdapter : AttributeAdapterBase<DateRangeFromTodayAttribute>
{
    public DateRangeFromTodayAttributeAdapter(DateRangeFromTodayAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer)
    {
    }

    public override void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-daterangefromtoday", GetErrorMessage(context));

        DateTime minDate = Attribute.GetMinDate();
        MergeAttribute(context.Attributes, "min", Attribute.IsDateOnly ? minDate.Date.ToString("yyyy-MM-dd") : minDate.ToString("yyyy-MM-dd HH:mm:ss.fff"));

        DateTime maxDate = Attribute.GetMaxDate();
        MergeAttribute(context.Attributes, "max", Attribute.IsDateOnly ? maxDate.Date.ToString("yyyy-MM-dd") : maxDate.ToString("yyyy-MM-dd HH:mm:ss.fff"));
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
        return Attribute.ErrorMessage;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.