Reputation: 33
I have a simple input model for my blazor server side component. I want to use the build in validation for two DateTime properties.
[DataType(DataType.Date)]
public DateTime? FromDate { get; set; }
[DataType(DataType.Date)]
public DateTime? ToDate { get; set; }
How can I only accept ToDate > FromDate?
Upvotes: 3
Views: 1898
Reputation: 11322
ValidationAttribute
s:DateMustBeAfterAttribute.cs:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DateMustBeAfterAttribute : ValidationAttribute
{
public DateMustBeAfterAttribute(string targetPropertyName)
=> TargetPropertyName = targetPropertyName;
public string TargetPropertyName { get; }
public string GetErrorMessage(string propertyName) =>
$"'{propertyName}' must be after '{TargetPropertyName}'.";
protected override ValidationResult? IsValid(
object? value, ValidationContext validationContext)
{
var targetValue = validationContext.ObjectInstance
.GetType()
.GetProperty(TargetPropertyName)
?.GetValue(validationContext.ObjectInstance, null);
if ((DateTime?)value < (DateTime?)targetValue)
{
var propertyName = validationContext.MemberName ?? string.Empty;
return new ValidationResult(GetErrorMessage(propertyName), new[] { propertyName });
}
return ValidationResult.Success;
}
}
DateMustBeBeforeAttribute.cs:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DateMustBeBeforeAttribute : ValidationAttribute
{
public DateMustBeBeforeAttribute(string targetPropertyName)
=> TargetPropertyName = targetPropertyName;
public string TargetPropertyName { get; }
public string GetErrorMessage(string propertyName) =>
$"'{propertyName}' must be before '{TargetPropertyName}'.";
protected override ValidationResult? IsValid(
object? value, ValidationContext validationContext)
{
var targetValue = validationContext.ObjectInstance
.GetType()
.GetProperty(TargetPropertyName)
?.GetValue(validationContext.ObjectInstance, null);
if ((DateTime?)value > (DateTime?)targetValue)
{
var propertyName = validationContext.MemberName ?? string.Empty;
return new ValidationResult(GetErrorMessage(propertyName), new[] { propertyName });
}
return ValidationResult.Success;
}
}
Usage:
public class DateTimeModel
{
[Required]
[DateMustBeBefore(nameof(ToDate))]
[DataType(DataType.Date)]
public DateTime? FromDate { get; set; }
[Required]
[DateMustBeAfter(nameof(FromDate))]
[DataType(DataType.Date)]
public DateTime? ToDate { get; set; }
}
The fields are linked so we need to notify EditContext
when any one of them changes to re-validate the other.
Example EditForm
:
<EditForm EditContext="editContext" OnInvalidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
From Date:
<InputDate TValue="DateTime?"
Value="dateTimeModel.FromDate"
ValueChanged="HandleFromDateChanged"
ValueExpression="() => dateTimeModel.FromDate" />
</label>
<ValidationMessage For="@(() => dateTimeModel.FromDate)" />
</p>
<p>
<label>
To Date:
<InputDate TValue="DateTime?"
Value="dateTimeModel.ToDate"
ValueChanged="HandleToDateChanged"
ValueExpression="() => dateTimeModel.ToDate" />
</label>
<ValidationMessage For="@(() => dateTimeModel.ToDate)" />
</p>
<button type="submit">Submit</button>
</EditForm>
@code {
private EditContext? editContext;
private DateTimeModel dateTimeModel = new();
protected override void OnInitialized()
{
editContext = new EditContext(dateTimeModel);
}
private void HandleFromDateChanged(DateTime? fromDate)
{
dateTimeModel.FromDate = fromDate;
if (editContext != null && dateTimeModel.ToDate != null)
{
FieldIdentifier toDateField = editContext.Field(nameof(DateTimeModel.ToDate));
editContext.NotifyFieldChanged(toDateField);
}
}
private void HandleToDateChanged(DateTime? toDate)
{
dateTimeModel.ToDate = toDate;
if (editContext != null && dateTimeModel.FromDate != null)
{
FieldIdentifier fromDateField = editContext.Field(nameof(DateTimeModel.FromDate));
editContext.NotifyFieldChanged(fromDateField);
}
}
private void HandleValidSubmit()
{
}
}
IValidatableObject
:To do more complex validation checks, your model can inherit from IValidatableObject
interface and implement the Validate
method:
public class ExampleModel : IValidatableObject
{
[DataType(DataType.Date)]
public DateTime? FromDate { get; set; }
[DataType(DataType.Date)]
public DateTime? ToDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (ToDate < FromDate)
{
yield return new ValidationResult("ToDate must be after FromDate", new[] { nameof(ToDate) });
}
}
}
Upvotes: 3