2009-08-30 18 views
9

Alcuni pannelli della mia pagina sono nascosti in determinate circostanze.Convalida condizionatamente parti di un modello MVC ASP.NET con DataAnnotations?

Ad esempio, potrei avere un "indirizzo di fatturazione" e un "indirizzo di spedizione" e non desidero convalidare "indirizzo di spedizione" se è selezionata una casella di controllo "ShippingSameAsBilling".

Sto cercando di utilizzare il nuovo DataAnnotations capabilities di ASP.NET MVC 2 (anteprima 1) per ottenere ciò.

Devo impedire la convalida dell '"indirizzo di spedizione" quando non è visualizzato e ho bisogno di trovare il modo per raggiungere questo obiettivo. Sto parlando principalmente lato server al contrario di using jquery.

Come posso ottenere questo risultato? Ho avuto diverse idee, legate al binding di modelli personalizzati, ma la mia migliore soluzione attuale è qui di seguito. Qualche feedback su questo metodo?

risposta

6

Per la CheckoutModel Sto usando questo approccio (la maggior parte dei campi nascosti):

[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; } 
} 

Il modello personalizzato legante rimuove tutti gli errori ModelState per i campi che iniziano con 'shippingAddress' se ne trova una. Quindi 'TryUpdateModel()' restituirà true.

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); 
       } 
      } 
     }  
    } 

Qualche soluzione migliore?

+2

Buona idea, ma non mi piace il pensiero di rimuovere gli errori dalla lista dopo che sono stati aggiunti. Preferirei non aggiungerli in primo luogo. –

2

posso vedere la vostra situazione. Sto cercando altre soluzioni di convalida anche per quanto riguarda le regole di convalida complesse che potrebbero applicarsi a più di una proprietà su un dato oggetto del modello o anche molte proprietà da diversi oggetti del modello in un oggetto grafico (se sei abbastanza sfortunato da convalidare oggetti collegati come questo).

La limitazione dell'interfaccia IDataErrorInfo è che un oggetto modello soddisfa lo stato valido semplicemente quando nessuna delle proprietà presenta errori. Ciò significa che un oggetto valido è un oggetto in cui tutte le proprietà sono anche valido. Tuttavia, potrei avere una situazione in cui se le proprietà A, B e C sono valide - allora l'intero oggetto è valido .. ma anche se la proprietà A non è valida ma B e C, allora l'oggetto soddisfa la validità. Semplicemente non ho modo di descrivere questa condizione/regola con gli attributi IDataErrorInfo/DataAnnotations.

Quindi ho trovato questo delegate approach. Ora molti degli utili progressi in MVC non esistevano al momento della stesura di questo articolo, ma il concetto base dovrebbe aiutarti. Anziché utilizzare gli attributi per definire le condizioni di convalida di un oggetto, creiamo funzioni delegate che convalidano requisiti più complessi e poiché sono delegati possiamo riutilizzarli. Certo è più lavoro, ma l'uso dei delegati significa che dovremmo essere in grado di scrivere il codice di una regola di validazione una volta e memorizzare tutte le regole di validazione in un unico posto (forse livello di servizio) e (il bit di kool) anche usare il MVC 2 DefaultModelBinder per invocare la convalida automaticamente (senza accumuli di controllo nelle azioni del controller, come il blog di Scott dice che possiamo fare con DataAnnotations. Fare riferimento a last paragraph prima dell'intestazione 'Strongly Typed UI Helpers')!Sono sicuro che si può rafforzare l'approccio suggerito nell'articolo precedente un po 'con delegati anonimi come Func<T> o Predicate<T> e scrivere blocchi di codice personalizzati per le regole di convalida abiliteranno le condizioni di proprietà incrociata (ad esempio la condizione che hai rinviato dove se la tua proprietà ShippingSameAsBilling è vera, puoi ignorare più regole per l'indirizzo di spedizione, ecc.).

DataAnnotations serve per rendere semplici le regole di convalida sugli oggetti veramente facile con pochissimo codice. Ma man mano che le tue esigenze si sviluppano, dovrai convalidare regole più complesse. I nuovi metodi virtuali nel raccoglitore modello MVC2 dovrebbero continuare a fornirci modi per integrare le nostre future invenzioni di convalida nel framework MVC.

2

Assicurarsi che i campi che non si desidera convalidare non siano registrati nell'azione. Convalidiamo solo i campi effettivamente pubblicati.

Edit: (da interlocutore)

Questo comportamento è cambiato in MVC2 RC2:

sistema di validazione di default convalida intero modello La validazione di default del sistema in ASP.NET MVC 1.0 e in anteprime di ASP.NET MVC 2 precedenti a RC 2 solo proprietà del modello convalidate che erano state inviate al server. In ASP.NET MVC 2, il nuovo comportamento è che tutte le proprietà del modello vengono convalidate quando il modello viene convalidato, indipendentemente dallo se è stato pubblicato un nuovo valore. Le applicazioni che dipendono dal comportamento di ASP.NET MVC 1.0 possono richiedere le modifiche . Per ulteriori informazioni su questa modifica, vedere la voce Input Validation vs. Model Validation in ASP.NET MVC sul blog di Brad Wilson.

0

Questo non è legato alla DataAnnotations ma avete guardato il progetto Fluent Validation? Ti dà un controllo a grana fine sulla tua convalida e se hai convalida da oggetto a oggetto un oggetto aggregato dei due oggetti ti farà andare avanti.

Anche sembra essere stato creato con MVC in mente, ma ha anche il suo "runtime" in modo da poterlo usare anche in altre applicazioni .NET che è un altro bonus nel mio libro.

1

Per i casi più complessi, ho spostato da Simple DataAnnotations al seguente: Validation with visitors and extension methods.

Se si vuole fare uso dei vostri DataAnnotations si dovrebbe sostituire qualcosa di simile al seguente:

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

con un metodo per convalidare una proprietà per nome tramite DataAnnotations (che non ho atm).

1

Ho creato un raccoglitore modello parziale che convalida solo le chiavi inviate. Per motivi di sicurezza (se avessi intenzione di fare un ulteriore passo avanti) creerei un attributo di annotazione dati che contrassegna i campi che possono essere esclusi da un modello.Quindi, gli attributi del campo di controllo OnModelUpdated assicurano che non vi siano segnalazioni indesiderate in corso.

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); 
     } 
    }  
} 
Problemi correlati