web-dev-qa-db-fra.com

Validation conditionnelle ASP.NET MVC

Comment utiliser des annotations de données pour effectuer une validation conditionnelle sur un modèle?

Par exemple, supposons que nous ayons le modèle suivant (Personne et Senior):

public class Person
{
    [Required(ErrorMessage = "*")]
    public string Name
    {
        get;
        set;
    }

    public bool IsSenior
    {
        get;
        set;
    }

    public Senior Senior
    {
        get;
        set;
    }
}

public class Senior
{
    [Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
    public string Description
    {
        get;
        set;
    }
}

Et la vue suivante:

<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>

<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>

<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>

J'aimerais être le champ obligatoire de la propriété "Senior.Description" en fonction du choix de la propriété "IsSenior" (true -> requis) Comment implémenter la validation conditionnelle dans ASP.NET MVC 2 avec des annotations de données?

118
Peter Stegnar

J'ai résolu ce problème en manipulant le "ModelState" dictionary, contenu dans le contrôleur. Le dictionnaire ModelState inclut tous les membres à valider.

Voici la solution:

Si vous devez implémenter une validation conditionnelle basée sur un champ (par exemple, si A = true, alors B est obligatoire), tout en conservant la messagerie d'erreur au niveau de la propriété (ceci n'est pas vrai pour les validateurs personnalisés qui sont: au niveau de l’objet) vous pouvez y parvenir en gérant "ModelState", en supprimant simplement les validations non souhaitées.

... En quelque classe ... 

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

[Required(ErrorMessage = "*")] 
public string DepentedProperty
{
  get;
  set;
}

... la classe continue ...

... Dans certaines actions du contrôleur ...

if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
   this.ModelState.Remove("DepentedProperty");
}

...

Avec cela, nous réalisons une validation conditionnelle, en laissant tout le reste identique.


METTRE À JOUR:

Ceci est ma dernière implémentation: j'ai utilisé une interface sur le modèle et l'attribut d'action qui valide le modèle qui implémente ladite interface. L'interface prescrit la méthode Validate (ModelStateDictionary modelState). L'attribut on action appelle simplement Validate (modelState) sur IValidatorSomething.

Je ne voulais pas compliquer cette réponse et je n’ai donc pas mentionné les détails de la mise en œuvre finale (qui, à la fin, sont importants dans le code de production).

61
Peter Stegnar

il existe un moyen bien meilleur d’ajouter des règles de validation conditionnelle dans MVC3. Faites en sorte que votre modèle hérite de IValidatableObject et implémente la méthode Validate:

public class Person : IValidatableObject
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
        if (IsSenior && string.IsNullOrEmpty(Senior.Description)) 
            yield return new ValidationResult("Description must be supplied.");
    }
}

voir plus de description sur http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx

141
viperguynaz

J'ai eu le même problème hier mais je l'ai fait de manière très propre, ce qui fonctionne à la fois pour la validation côté client et côté serveur.

Condition: En fonction de la valeur d'une autre propriété dans le modèle, vous souhaitez qu'une autre propriété soit requise. Voici le code

public class RequiredIfAttribute : RequiredAttribute
{
    private String PropertyName { get; set; }
    private Object DesiredValue { get; set; }

    public RequiredIfAttribute(String propertyName, Object desiredvalue)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (proprtyvalue.ToString() == DesiredValue.ToString())
        {
            ValidationResult result = base.IsValid(value, context);
            return result;
        }
        return ValidationResult.Success;
    }
}

Ici PropertyName est la propriété sur laquelle vous voulez créer votre condition DesiredValue est la valeur particulière de PropertyName (propriété) pour laquelle votre autre propriété doit être validée.

Disons que vous avez le suivant

public class User
{
    public UserType UserType { get; set; }

    [RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
    public string Password
    {
        get;
        set;
    }
}

Enfin, inscrivez l'adaptateur pour votre attribut afin qu'il puisse effectuer la validation côté client (je le mets dans global.asax, Application_Start).

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
33
Dan Hunex

J'utilise cette incroyable pépite qui effectue des annotations dynamiques ExpressiveAnnotations

Vous pouvez valider n'importe quelle logique dont vous pouvez rêver:

public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
27
Korayem

Vous pouvez désactiver les validateurs de manière conditionnelle en supprimant les erreurs de ModelState:

ModelState["DependentProperty"].Errors.Clear();
17
Pavel Chuchuva

Merci Merritt :)

Je viens de mettre à jour ceci vers MVC 3 au cas où quelqu'un le trouverait utile; http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp -net-mvc-3.aspx

Simon

8
Simon Ince

Il existe maintenant un framework qui effectue cette validation conditionnelle (parmi d'autres validations pratiques d'annotation de données): http://foolproof.codeplex.com/

Plus précisément, consultez le validateur [RequiredIfTrue ("IsSenior")]. Vous mettez cela directement sur la propriété que vous souhaitez valider, de sorte que vous obtenez le comportement souhaité de l’erreur de validation associée à la propriété "Senior".

Il est disponible sous forme de package NuGet.

6
bojingo

J'ai eu le même problème, avait besoin d'une modification de l'attribut [Obligatoire] - champ obligatoire en fonction de la requête http. La solution était similaire à la réponse de Dan Hunex, mais sa solution ne fonctionnait pas correctement (voir commentaires). Je n'utilise pas la validation discrète, mais simplement MicrosoftMvcValidation.js prêt à l'emploi . Implémentez votre attribut personnalisé:

public class RequiredIfAttribute : RequiredAttribute
{

    public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
    {

    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {

    //You can put your logic here   

        return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
    }


}

Ensuite, vous devez implémenter votre fournisseur personnalisé pour l'utiliser comme adaptateur dans votre global.asax.

public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{

    ControllerContext ccontext;
    public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
       : base(metadata, context, attribute)
    {
        ccontext = context;// I need only http request
    }

//override it for custom client-side validation 
     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
     {       
               //here you can customize it as you want
         ModelClientValidationRule rule = new ModelClientValidationRule()
         {
             ErrorMessage = ErrorMessage,
    //and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"    
             ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
         };
         return new ModelClientValidationRule[] { rule };
      }
}

Et modifiez votre global.asax avec une ligne

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));

et le voici

[RequiredIf]
public string NomenclatureId { get; set; }

Le principal avantage pour moi est que je n'ai pas à coder un validateur client personnalisé, comme dans le cas d'une validation non intrusive. cela fonctionne comme [Obligatoire], mais seulement dans les cas que vous voulez.

3
Den

Vous devez valider au niveau Personne, pas au niveau Senior, ou Senior doit avoir une référence à sa personne-mère. Il me semble que vous avez besoin d'un mécanisme d'auto-validation qui définit la validation sur la personne et non sur l'une de ses propriétés. Je ne suis pas sûr, mais je ne pense pas que DataAnnotations prenne en charge cette solution immédiatement. Vous pouvez créer votre propre Attribute dérivée de ValidationAttribute pouvant être décorée au niveau de la classe, puis créer un validateur personnalisé permettant également l’exécution de ces validateurs.

Je sais que Validation Application Block prend en charge l'auto-validation immédiate, mais VAB nécessite une courbe d'apprentissage assez abrupte. Néanmoins, voici un exemple utilisant VAB:

[HasSelfValidation]
public class Person
{
    public string Name { get; set; }
    public bool IsSenior { get; set; }
    public Senior Senior { get; set; }

    [SelfValidation]
    public void ValidateRange(ValidationResults results)
    {
        if (this.IsSenior && this.Senior != null && 
            string.IsNullOrEmpty(this.Senior.Description))
        {
            results.AddResult(new ValidationResult(
                "A senior description is required", 
                this, "", "", null));
        }
    }
}
3
Steven

Découvrez ce gars:

http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx

Je travaille actuellement sur son exemple de projet.

2
Merritt

Utilisation typique pour l'élimination conditionnelle de l'erreur de l'état du modèle:

  1. Faire une première partie conditionnelle de l'action du contrôleur
  2. Exécuter une logique pour supprimer une erreur de ModelState
  3. Effectuer le reste de la logique existante (généralement validation de l'état du modèle, puis tout le reste)

Exemple:

public ActionResult MyAction(MyViewModel vm)
{
    // perform conditional test
    // if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")

    // Do typical model state validation, inside following if:
    //     if (!ModelState.IsValid)

    // Do rest of logic (e.g. fetching, saving

Dans votre exemple, conservez tout tel quel et ajoutez la logique suggérée à l'action de votre contrôleur. Je suppose que votre ViewModel transmis à l'action du contrôleur contient les objets Personne et Personne Senior contenant des données renseignées à partir de l'interface utilisateur.

0
Jeremy Ray Brown

J'utilise MVC 5 mais vous pouvez essayer quelque chose comme ceci:

public DateTime JobStart { get; set; }

[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }

Dans votre cas, vous diriez quelque chose comme "IsSenior == true" . Il vous suffira ensuite de vérifier la validation de votre action post.

0
fosterImposter