Existe-t-il un moyen de créer un attribut personnalisé dans ASP.NET Core pour valider si une propriété de date est inférieure à une autre propriété de date dans un modèle en utilisant ValidationAttribute
.
Disons que j'ai ceci:
public class MyViewModel
{
[Required]
[CompareDates]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");
}
J'essaie d'utiliser quelque chose comme ça:
public class CompareDates : ValidationAttribute
{
public CompareDates()
: base("") { }
public override bool IsValid(object value)
{
return base.IsValid(value);
}
}
J'ai trouvé un autre SO post qui propose d'utiliser une autre bibliothèque, mais je préfère m'en tenir à ValidationAttribute si c'était faisable.
Vous pouvez créer un attribut de validation personnalisé pour comparer deux propriétés. C'est une validation côté serveur:
public class MyViewModel
{
[DateLessThan("End", ErrorMessage = "Not valid")]
public DateTime Begin { get; set; }
public DateTime End { get; set; }
}
public class DateLessThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public DateLessThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var currentValue = (DateTime)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
var comparisonValue = (DateTime)property.GetValue(validationContext.ObjectInstance);
if (currentValue > comparisonValue)
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
Mise à jour : Si vous avez besoin d'une validation côté client pour cet attribut, vous devez implémenter une interface IClientModelValidator
:
public class DateLessThanAttribute : ValidationAttribute, IClientModelValidator
{
...
public void AddValidation(ClientModelValidationContext context)
{
var error = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-error", error);
}
}
La méthode AddValidation
ajoutera des attributs à vos entrées depuis context.Attributes
.
Vous pouvez en savoir plus ici IClientModelValidator
Comme une option possible auto-validation:
Vous avez juste besoin d'implémenter une interface IValidatableObject
avec la méthode Validate
, où vous pouvez mettre votre code de validation.
public class MyViewModel : IValidatableObject
{
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
int result = DateTime.Compare(StartDate , EndDate);
if (result < 0)
{
yield return new ValidationResult("start date must be less than the end date!", new [] { "ConfirmEmail" });
}
}
}
Sur la base de la réponse d'Alexander Gore, je suggère une validation meilleure et générique (et elle est compatible avec le noyau .Net). Lorsque vous souhaitez comparer des propriétés à l'aide de la logique GreatherThan ou LessThan (quels que soient les types), vous pouvez valider si elles ont implémenté l'interface IComparable
. Si les deux propriétés sont valides, vous pouvez utiliser l'implémentation CompareTo
. Cette règle s'applique également aux types DateTime
et aux nombres
LessThan
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class LessThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public LessThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
if (currentValue.CompareTo((IComparable)comparisonValue) >= 0)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
GreaterThan
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class GreaterThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public GreaterThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
if (currentValue.CompareTo((IComparable)comparisonValue) < 0)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Dans un contexte de réservation, un exemple pourrait être le suivant:
public DateTime CheckInDate { get; set; }
[GreaterThan("CheckInDate", ErrorMessage = "CheckOutDate must be greater than CheckInDate")]
public DateTime CheckOutDate { get; set; }
Vous pouvez comparer les deux dates dans la méthode IsValid.
public class CompareDates : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
//get your startdate & end date from model and value
//perform comparison
if (StartDate < EndDate)
{
return new ValidationResult
("start date must be less than the end date");
}
else
{
return ValidationResult.Success;
}
}
}
Basé sur la réponse de Jaime et le commentaire de Jeffrey concernant la nécessité d'avoir un seul attribut pour inférieur à, inférieur à ou égal à, égal à, supérieur à, supérieur à ou égal à.
Le code ci-dessous gérera toutes les conditions avec un seul attribut.
public enum ComparisonType
{
LessThan,
LessThanOrEqualTo,
EqualTo,
GreaterThan,
GreaterThanOrEqualTo
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ComparisonAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
private readonly ComparisonType _comparisonType;
public ComparisonAttribute(string comparisonProperty, ComparisonType comparisonType)
{
_comparisonProperty = comparisonProperty;
_comparisonType = comparisonType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable) value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
bool compareToResult;
switch (_comparisonType)
{
case ComparisonType.LessThan:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) >= 0;
break;
case ComparisonType.LessThanOrEqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) > 0;
break;
case ComparisonType.EqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) != 0;
break;
case ComparisonType.GreaterThan:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) <= 0;
break;
case ComparisonType.GreaterThanOrEqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) < 0;
break;
default:
throw new ArgumentOutOfRangeException();
}
return compareToResult ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
}
}
Dans le contexte de la réservation, un exemple serait le suivant:
public DateTime CheckInDate { get; set; }
[Comparison("CheckInDate", ComparisonType.EqualTo, ErrorMessage = "CheckOutDate must be equal to CheckInDate")]
public DateTime CheckOutDate { get; set; }