2009-08-30 8 views
9

J'ai certains panneaux sur ma page qui sont cachés dans certaines circonstances. Par exemple, je peux avoir une «adresse de facturation» et une «adresse de livraison» et je ne veux pas valider «adresse de livraison» si une case «ShippingSameAsBilling» est cochée.Validation conditionnelle de portions d'un modèle ASP.NET MVC avec DataAnnotations?

J'essaie d'utiliser le nouveau DataAnnotations capabilities de ASP.NET MVC 2 (aperçu 1) pour y parvenir.

Je dois empêcher la validation de «l'adresse de livraison» quand elle n'est pas affichée et doit trouver le moyen d'y parvenir. Je parle principalement côté serveur par opposition à using jquery.

Comment puis-je y parvenir? J'ai eu plusieurs idées, liées à la liaison de modèle personnalisé, mais ma meilleure solution actuelle est ci-dessous. Des commentaires sur cette méthode?

Répondre

6

Pour le CheckoutModel J'utilise cette approche (la plupart des champs cachés):

[ModelBinder(typeof(CheckoutModelBinder))] 
public class CheckoutModel : ShoppingCartModel 
{   
    public Address BillingAddress { get; set; } 
    public Address ShippingAddress { get; set; } 
    public bool ShipToBillingAddress { get; set; } 
} 

public class Address 
{ 
    [Required(ErrorMessage = "Email is required")] 
    public string Email { get; set; } 

    [Required(ErrorMessage = "First name is required")] 
    public string FirstName { get; set; } 

    [Required()] 
    public string LastName { get; set; } 

    [Required()] 
    public string Address1 { get; set; } 
} 

Le liant modèle personnalisé supprime toutes les erreurs de ModelState pour les champs commençant par « ShippingAddress » si elle en trouve. Then 'TryUpdateModel()' retournera vrai.

public class CheckoutModelBinder : DefaultModelBinder 
    { 
     protected override void OnModelUpdated(ControllerContext controllerContext, 
               ModelBindingContext bindingContext) { 

      base.OnModelUpdated(controllerContext, bindingContext); 

      var model = (CheckoutModel)bindingContext.Model; 

      // if user specified Shipping and Billing are the same then 
      // remove all ModelState errors for ShippingAddress 
      if (model.ShipToBillingAddress) 
      { 
       var keys = bindingContext.ModelState.Where(x => x.Key.StartsWith("ShippingAddress")).Select(x => x.Key).ToList(); 
       foreach (var key in keys) 
       { 
        bindingContext.ModelState.Remove(key); 
       } 
      } 
     }  
    } 

De meilleures solutions?

+2

Bonne idée mais je n'aime pas l'idée de supprimer les erreurs de la liste après leur ajout. Je préfère ne pas les ajouter en premier lieu. –

2

Je peux voir votre situation. Je suis à la recherche d'autres solutions de validation concernant les règles de validation complexes qui peuvent s'appliquer à plusieurs propriétés d'un objet modèle donné ou même à plusieurs propriétés d'objets modèles différents dans un graphe d'objets (si vous êtes assez malchanceux pour valider des objets liés comme ça).

La limitation de l'interface IDataErrorInfo est qu'un objet de modèle satisfait l'état valide simplement lorsqu'aucune des propriétés n'a d'erreur. C'est-à-dire qu'un valide objet est un où toutes ses propriétés sont également valide. Cependant, je peux avoir une situation où si la propriété A, B et C sont valides - alors l'objet entier est valide mais aussi si la propriété A n'est pas valide mais B et C sont, alors l'objet satisfait la validité. Je n'ai tout simplement aucun moyen de décrire cette condition/règle avec les attributs IDataErrorInfo interface/DataAnnotations. J'ai trouvé ce delegate approach. Maintenant, beaucoup d'avancées utiles dans MVC n'existaient pas au moment de la rédaction de cet article, mais le concept de base devrait vous aider. Plutôt que d'utiliser des attributs pour définir les conditions de validation d'un objet, nous créons des fonctions déléguées qui valident des exigences plus complexes et, comme elles sont déléguées, nous pouvons les réutiliser. Bien sûr c'est plus de travail, mais l'utilisation des délégués signifie que nous devrions être en mesure d'écrire le code de la règle de validation une fois et stocker toutes les règles de validation dans un endroit (peut-être la couche de service) et (le bit kool) même utiliser le MVC 2 DefaultModelBinder pour invoquer la validation automatiquement (sans beaucoup de vérification dans nos actions de contrôleur - comme le blog de Scott dit que nous pouvons faire avec DataAnnotations. Reportez-vous à la last paragraph avant le titre «Strongly Typed UI Helpers»)!Je suis sûr que vous pouvez renforcer l'approche suggérée dans l'article ci-dessus avec des délégués anonymes comme Func<T> ou Predicate<T> et écrire des blocs de code personnalisés pour les règles de validation permettra des conditions de propriété croisée (par exemple la condition que vous avez référée à où si votre propriété ShippingSameAsBilling est vraie, vous pouvez ignorer plus de règles pour l'adresse de livraison, etc).

DataAnnotations sert à faire des règles de validation simples sur les objets vraiment facile avec très peu de code. Mais au fur et à mesure que vos exigences se développent, vous devrez valider des règles plus complexes. Les nouvelles méthodes virtuelles dans le classeur modèle MVC2 devraient continuer à nous fournir des moyens d'intégrer nos futures inventions de validation dans le cadre MVC.

2

Assurez-vous que les champs que vous ne souhaitez pas être validés ne sont pas enregistrés dans l'action. Nous ne validons que les champs qui ont été effectivement affichés.

Edit: (par questionneur)

Ce comportement a changé MVC2 RC2:

système de validation par défaut valide modèle entier Le système de validation par défaut dans ASP.NET MVC 1.0 et aperçus de ASP.NET MVC 2 avant RC 2 validé uniquement les propriétés du modèle ont été publiées sur le serveur. Dans ASP.NET MVC 2, le nouveau comportement est que toutes les propriétés du modèle sont validées lorsque le modèle est validé, indépendamment de si une nouvelle valeur a été validée. Les applications qui dépendent du comportement ASP.NET MVC 1.0 peuvent nécessiter des modifications . Pour plus d'informations sur ce changement, voir l'entrée Input Validation vs. Model Validation dans ASP.NET MVC sur le blog de Brad Wilson.

0

Ce n'est pas lié à DataAnnotations, mais avez-vous regardé le projet Fluent Validation? Il vous donne un contrôle fin de votre validation et si vous avez une validation objet-objet, un objet agrégé des deux objets vous permettra de démarrer.

Aussi, il semble avoir été construit avec MVC à l'esprit, mais il a aussi son propre "runtime" de sorte que vous pouvez l'utiliser dans d'autres applications .NET qui est un autre bonus dans mon livre.

1

Pour les cas les plus complexes, je suis passé de simple DataAnnotations à ce qui suit: Validation with visitors and extension methods.

Si vous voulez utiliser votre DataAnnotations vous remplacer quelque chose comme ce qui suit:

public IEnumerable<ErrorInfo> BrokenRules (Payment payment) 
{ 
    // snip... 
    if (string.IsNullOrEmpty (payment.CCName)) 
    { 
     yield return new ErrorInfo ("CCName", "Credit card name is required"); 
    } 
} 

une méthode pour valider une propriété par nom via DataAnnotations (que je n'ai pas atm).

1

J'ai créé un classeur de modèle partiel qui valide uniquement les clés qui ont été soumises. Pour des raisons de sécurité (si j'allais aller un peu plus loin), je créerais un attribut d'annotation de données qui marquerait quels champs sont autorisés à être exclus d'un modèle.Ensuite, onModelUpdated vérifie les attributs des champs pour s'assurer qu'il n'y a pas de sous-dépôt indésirable.

public class PartialModelBinder : DefaultModelBinder 
{ 
    protected override void OnModelUpdated(ControllerContext controllerContext, 
     ModelBindingContext bindingContext) 
    { 
     // default model binding to get errors 
     base.OnModelUpdated(controllerContext, bindingContext); 

     // remove errors from filds not posted 
     // TODO: include request files 
     var postedKeys = controllerContext.HttpContext.Request.Form.AllKeys; 
     var unpostedKeysWithErrors = bindingContext.ModelState 
      .Where(i => !postedKeys.Contains(i.Key)) 
      .Select(i=> i.Key).ToList(); 
     foreach (var key in unpostedKeysWithErrors) 
     { 
      bindingContext.ModelState.Remove(key); 
     } 
    }  
}