45 votes

Validation de modèles personnalisés de propriétés dépendantes à l'aide d'annotations de données

Depuis, j'utilise l'excellent FluentValidation pour valider mes classes de modèles. Dans les applications web, je l'utilise en conjonction avec la bibliothèque jquery.validate pour effectuer la validation côté client également. L'inconvénient est qu'une grande partie de la logique de validation est répétée du côté client et n'est plus centralisée à un seul endroit.

C'est pourquoi je cherche une alternative. Il existe muchos exemples montrant l'utilisation des annotations de données pour effectuer la validation du modèle. Cela semble très prometteur. Une chose que je n'ai pas trouvée est comment valider une propriété qui dépend de la valeur d'une autre propriété.

Prenons par exemple le modèle suivant :

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }
    [Required]
    public DateTime? EndDate { get; set; }
}

Je voudrais m'assurer que EndDate est supérieure à StartDate . Je pourrais écrire un attribut de validation étendant Attribut de validation afin d'exécuter une logique de validation personnalisée. Malheureusement, je n'ai pas trouvé de moyen d'obtenir l'information suivante instance du modèle :

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        // value represents the property value on which this attribute is applied
        // but how to obtain the object instance to which this property belongs?
        return true;
    }
}

J'ai trouvé que le Attribut de validation personnalisé semble faire l'affaire car il a cette ValidationContext qui contient l'instance de l'objet à valider. Malheureusement, cet attribut n'a été ajouté qu'en .NET 4.0. Ma question est donc la suivante : puis-je réaliser la même fonctionnalité en .NET 3.5 SP1 ?


UPDATE :

Il semble que FluentValidation supporte déjà validation côté client et métadonnées dans ASP.NET MVC 2.

Il serait néanmoins bon de savoir si les annotations de données peuvent être utilisées pour valider les propriétés dépendantes.

29voto

Travis Illig Points 6435

MVC2 est fourni avec un exemple "PropertiesMustMatchAttribute" qui montre comment faire fonctionner les DataAnnotations pour vous et qui devrait fonctionner à la fois dans .NET 3.5 et .NET 4.0. Cet exemple de code ressemble à ceci :

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
    private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";

    private readonly object _typeId = new object();

    public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
        : base(_defaultErrorMessage)
    {
        OriginalProperty = originalProperty;
        ConfirmProperty = confirmProperty;
    }

    public string ConfirmProperty
    {
        get;
        private set;
    }

    public string OriginalProperty
    {
        get;
        private set;
    }

    public override object TypeId
    {
        get
        {
            return _typeId;
        }
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
            OriginalProperty, ConfirmProperty);
    }

    public override bool IsValid(object value)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
        object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
        object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
        return Object.Equals(originalValue, confirmValue);
    }
}

Lorsque vous utilisez cet attribut, plutôt que de le placer sur une propriété de votre classe modèle, vous le placez sur la classe elle-même :

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
    public string NewPassword { get; set; }
    public string ConfirmPassword { get; set; }
}

Lorsque "IsValid" est appelé sur votre attribut personnalisé, l'instance entière du modèle lui est transmise, de sorte que vous pouvez obtenir les valeurs des propriétés dépendantes de cette façon. Vous pourriez facilement suivre ce modèle pour créer un attribut de comparaison de date, ou même un attribut de comparaison plus général.

Brad Wilson en donne un bon exemple sur son blog montrant comment ajouter la partie de la validation côté client, bien que je ne sois pas sûr que cet exemple fonctionne à la fois dans .NET 3.5 et .NET 4.0.

15voto

Nick Riggs Points 1051

J'ai eu ce même problème et j'ai récemment mis ma solution en libre accès : http://foolproof.codeplex.com/

La solution de Foolproof pour l'exemple ci-dessus serait :

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}

7voto

orcy Points 678

Au lieu de l'attribut PropertiesMustMatch, c'est l'attribut CompareAttribute qui peut être utilisé dans MVC3. Selon ce lien http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1 :

public class RegisterModel
{
    // skipped

    [Required]
    [ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }                       

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
    public string ConfirmPassword { get; set; }
}

CompareAttribute est un nouveau validateur très utile qui n'est pas réellement partie de System.ComponentModel.DataAnnotations, mais a été ajouté à la System.Web.Mvc DLL par l'équipe. Alors que pas particulièrement bien nommé (la seule comparaison qu'il effectue est de vérifier l'égalité l'égalité, donc peut-être que EqualTo serait plus plus évident), il est facile de voir à partir de l'utilisation que ce validateur vérifie que la valeur d'une propriété est égale à la valeur d'une autre propriété. Vous pouvez voir dans le code, que l'attribut prend en compte une propriété de type chaîne de caractères qui est le nom de l'autre propriété que que vous comparez. L'utilisation classique de ce type de validateur est ce que nous l'utilisons ici : le mot de passe confirmation.

4voto

Jaroslaw Waliszko Points 6618

Il s'est écoulé un peu de temps depuis que votre question a été posée, mais si vous aimez toujours les métadonnées (du moins parfois), il existe ci-dessous une autre solution alternative, qui vous permet de fournir diverses expressions logiques aux attributs :

[Required]
public DateTime? StartDate { get; set; }    
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }

Il fonctionne aussi bien du côté serveur que du côté client. Plus de détails peuvent être trouvés ici .

3voto

Steven Points 56939

Étant donné que les méthodes des DataAnnotations de .NET 3.5 ne vous permettent pas de fournir l'objet réel validé ou un contexte de validation, vous devrez recourir à un peu d'astuce pour y parvenir. Je dois admettre que je ne suis pas familier avec ASP.NET MVC, donc je ne peux pas dire comment faire cela exactement en conjonction avec MCV, mais vous pouvez essayer d'utiliser une valeur thread-static pour passer l'argument lui-même. Voici un exemple avec quelque chose qui pourrait fonctionner.

Créez d'abord une sorte de "portée d'objet" qui vous permet de faire circuler des objets sans avoir à les faire passer par la pile d'appels :

public sealed class ContextScope : IDisposable 
{
    [ThreadStatic]
    private static object currentContext;

    public ContextScope(object context)
    {
        currentContext = context;
    }

    public static object CurrentContext
    {
        get { return context; }
    }

    public void Dispose()
    {
        currentContext = null;
    }
}

Ensuite, créez votre validateur pour utiliser le ContextScope :

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
         Event e = (Event)ObjectContext.CurrentContext;

         // validate event here.
    }
}

Et enfin, assurez-vous que l'objet passe dans le ContextScope :

Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}

Est-ce utile ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X