我们有一个.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在午夜回收,这已经缓解了这个问题,但对我来说仍然没有意义。
我不知道这对我有多大帮助,因为我对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;
}
}