Je comprends que cela IValidatableObject
sert à valider un objet d'une manière qui permet de comparer les propriétés les unes par rapport aux autres.
J'aimerais toujours avoir des attributs pour valider les propriétés individuelles, mais je veux ignorer les échecs sur certaines propriétés dans certains cas.
Est-ce que j'essaye de ne pas l'utiliser correctement dans le cas ci-dessous? Sinon, comment puis-je mettre en œuvre cela?
public class ValidateMe : IValidatableObject
{
[Required]
public bool Enable { get; set; }
[Range(1, 5)]
public int Prop1 { get; set; }
[Range(1, 5)]
public int Prop2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!this.Enable)
{
/* Return valid result here.
* I don't care if Prop1 and Prop2 are out of range
* if the whole object is not "enabled"
*/
}
else
{
/* Check if Prop1 and Prop2 meet their range requirements here
* and return accordingly.
*/
}
}
}
Citation du billet de blog de Jeff Handley sur les objets et propriétés de validation avec Validator :
Cela indique que ce que vous essayez de faire ne fonctionnera pas directement car la validation sera abandonnée à l'étape 2. Vous pouvez essayer de créer des attributs qui héritent des attributs intégrés et vérifier spécifiquement la présence d'une propriété activée (via une interface) avant d'effectuer leur validation normale. Vous pouvez également mettre toute la logique de validation de l'entité dans la
Validate
méthode.Vous pouvez également jeter un œil à l'implémentation exacte de la
Validator
classe icila source
Juste pour ajouter quelques points:
Étant donné que la
Validate()
signature de la méthode retourneIEnumerable<>
, celayield return
peut être utilisé pour générer les résultats paresseusement - cela est avantageux si certains des contrôles de validation sont gourmands en E / S ou en CPU.public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (this.Enable) { // ... if (this.Prop1 > this.Prop2) { yield return new ValidationResult("Prop1 must be larger than Prop2"); }
De plus, si vous utilisez
MVC ModelState
, vous pouvez convertir les échecs de résultats de validation enModelState
entrées comme suit (cela peut être utile si vous effectuez la validation dans un classeur de modèles personnalisé ):var resultsGroupedByMembers = validationResults .SelectMany(vr => vr.MemberNames .Select(mn => new { MemberName = mn ?? "", Error = vr.ErrorMessage })) .GroupBy(x => x.MemberName); foreach (var member in resultsGroupedByMembers) { ModelState.AddModelError( member.Key, string.Join(". ", member.Select(m => m.Error))); }
la source
J'ai implémenté une classe abstraite d'usage général pour la validation
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace App.Abstractions { [Serializable] abstract public class AEntity { public int Id { get; set; } public IEnumerable<ValidationResult> Validate() { var vResults = new List<ValidationResult>(); var vc = new ValidationContext( instance: this, serviceProvider: null, items: null); var isValid = Validator.TryValidateObject( instance: vc.ObjectInstance, validationContext: vc, validationResults: vResults, validateAllProperties: true); /* if (true) { yield return new ValidationResult("Custom Validation","A Property Name string (optional)"); } */ if (!isValid) { foreach (var validationResult in vResults) { yield return validationResult; } } yield break; } } }
la source
Le problème avec la réponse acceptée est qu'elle dépend désormais de l'appelant pour que l'objet soit correctement validé. Je supprimerais le RangeAttribute et ferais la validation de plage dans la méthode Validate ou je créerais un attribut personnalisé sous-classant RangeAttribute qui prend le nom de la propriété requise comme argument sur le constructeur.
Par exemple:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] class RangeIfTrueAttribute : RangeAttribute { private readonly string _NameOfBoolProp; public RangeIfTrueAttribute(string nameOfBoolProp, int min, int max) : base(min, max) { _NameOfBoolProp = nameOfBoolProp; } public RangeIfTrueAttribute(string nameOfBoolProp, double min, double max) : base(min, max) { _NameOfBoolProp = nameOfBoolProp; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var property = validationContext.ObjectType.GetProperty(_NameOfBoolProp); if (property == null) return new ValidationResult($"{_NameOfBoolProp} not found"); var boolVal = property.GetValue(validationContext.ObjectInstance, null); if (boolVal == null || boolVal.GetType() != typeof(bool)) return new ValidationResult($"{_NameOfBoolProp} not boolean"); if ((bool)boolVal) { return base.IsValid(value, validationContext); } return null; } }
la source
J'ai aimé la réponse de cocogza, sauf que l'appel de base.IsValid a entraîné une exception de débordement de pile car il ré-entrerait encore et encore la méthode IsValid. Donc je l'ai modifié pour être pour un type de validation spécifique, dans mon cas c'était pour une adresse e-mail.
[AttributeUsage(AttributeTargets.Property)] class ValidEmailAddressIfTrueAttribute : ValidationAttribute { private readonly string _nameOfBoolProp; public ValidEmailAddressIfTrueAttribute(string nameOfBoolProp) { _nameOfBoolProp = nameOfBoolProp; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (validationContext == null) { return null; } var property = validationContext.ObjectType.GetProperty(_nameOfBoolProp); if (property == null) { return new ValidationResult($"{_nameOfBoolProp} not found"); } var boolVal = property.GetValue(validationContext.ObjectInstance, null); if (boolVal == null || boolVal.GetType() != typeof(bool)) { return new ValidationResult($"{_nameOfBoolProp} not boolean"); } if ((bool)boolVal) { var attribute = new EmailAddressAttribute {ErrorMessage = $"{value} is not a valid e-mail address."}; return attribute.GetValidationResult(value, validationContext); } return null; } }
Cela fonctionne beaucoup mieux! Il ne plante pas et produit un joli message d'erreur. J'espère que cela aide quelqu'un!
la source
Ce que je n'aime pas à propos d'iValidate, c'est qu'il semble ne fonctionner qu'APRÈS toutes les autres validations.
De plus, au moins sur notre site, il s'exécuterait à nouveau lors d'une tentative de sauvegarde. Je vous suggère simplement de créer une fonction et d'y placer tout votre code de validation. Alternativement, pour les sites Web, vous pouvez avoir votre validation «spéciale» dans le contrôleur après la création du modèle. Exemple:
public ActionResult Update([DataSourceRequest] DataSourceRequest request, [Bind(Exclude = "Terminal")] Driver driver) { if (db.Drivers.Where(m => m.IDNumber == driver.IDNumber && m.ID != driver.ID).Any()) { ModelState.AddModelError("Update", string.Format("ID # '{0}' is already in use", driver.IDNumber)); } if (db.Drivers.Where(d => d.CarrierID == driver.CarrierID && d.FirstName.Equals(driver.FirstName, StringComparison.CurrentCultureIgnoreCase) && d.LastName.Equals(driver.LastName, StringComparison.CurrentCultureIgnoreCase) && (driver.ID == 0 || d.ID != driver.ID)).Any()) { ModelState.AddModelError("Update", "Driver already exists for this carrier"); } if (ModelState.IsValid) { try {
la source