2009-08-30 16 views
9

Ich habe bestimmte Panels auf meiner Seite, die unter bestimmten Umständen versteckt sind.Bedingte Validierung von Teilen eines ASP.NET MVC-Modells mit DataAnnotations?

Zum Beispiel könnte ich eine 'Rechnungsadresse' und 'Lieferadresse' haben und ich möchte nicht 'Lieferadresse' validieren, wenn ein 'ShippingSameAsBilling' Kontrollkästchen aktiviert ist.

Ich versuche, das neue DataAnnotations capabilities von ASP.NET MVC 2 (Vorschau 1) zu verwenden, um dies zu erreichen.

Ich muss die Validierung der 'Lieferadresse' verhindern, wenn sie nicht angezeigt wird und den Weg dafür finden muss. Ich spreche hauptsächlich Server-Seite im Gegensatz zu using jquery.

Wie kann ich das erreichen? Ich hatte verschiedene Ideen im Zusammenhang mit der Bindung von benutzerdefinierten Modellen, aber meine derzeit beste Lösung ist unten aufgeführt. Irgendwelche Rückmeldungen zu dieser Methode?

Antwort

6

Für die CheckoutModel ich diesen Ansatz bin mit (den meisten Feldern versteckt):

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

Das Bindemittel benutzerdefinierte Modell alle Modelfehler für Felder entfernt mit ‚Shipping‘ beginnen, wenn es welche findet. Dann wird 'TryUpdateModel()' true zurückgeben.

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

Irgendwelche besseren Lösungen?

+2

Gute Idee, aber ich mag den Gedanken nicht, die Fehler von der Liste zu entfernen, nachdem sie hinzugefügt wurden. Ich würde sie lieber nicht hinzufügen. –

2

kann ich Ihre missliche Lage sehen. Ich suche nach anderen Validierungslösungen auch im Hinblick auf komplexe Validierungsregeln, die für mehr als eine Eigenschaft auf einem bestimmten Modellobjekt oder sogar für viele Eigenschaften von verschiedenen Modellobjekten in einem Objektdiagramm gelten können (wenn Sie unglücklicherweise genug haben, verknüpfte Objekte zu validieren) so was).

Die Einschränkung der Schnittstelle IDataErrorInfo ist, dass ein Modellobjekt den gültigen Status erfüllt, wenn keine der Eigenschaften Fehler aufweist. Dies bedeutet, dass ein gültiges Objekt ist eines, wo alle seine Eigenschaften auch gültig sind. Jedoch kann ich eine Situation haben, in der, wenn Eigenschaft A, B und C gültig sind - dann das ganze Objekt gültig ist. aber auch wenn Eigenschaft A nicht gültig ist, aber B und C sind, dann erfüllt das Objekt die Gültigkeit. Ich habe einfach keine Möglichkeit, diese Bedingung/Regel mit den Attributen IDataErrorInfo interface/DataAnnotations zu beschreiben.

So fand ich diese delegate approach. Jetzt gab es viele hilfreiche Verbesserungen in MVC zum Zeitpunkt der Erstellung dieses Artikels nicht, aber das Kernkonzept sollte Ihnen helfen. Anstatt Attribute zur Definition der Validierungsbedingungen eines Objekts zu verwenden, erstellen wir Delegatfunktionen, die komplexere Anforderungen validieren, und weil sie delegiert sind, können wir sie wiederverwenden. Sicher, es ist mehr Arbeit, aber die Verwendung von Delegaten bedeutet, dass wir in der Lage sein sollten, schreiben Validierungsregel Code einmal und Speichern Sie alle Validierungsregeln an der einen Stelle (vielleicht Service-Schicht) und (das kool Bit) sogar verwenden MVC 2 DefaultModelBinder, um die Überprüfung automatisch aufzurufen (ohne jede Menge Kontrolle in unseren Controller-Aktionen - wie Scott's Blog sagt, können wir mit DataAnnotations machen. Siehe die last paragraph vor der Überschrift 'Stark typisierte UI-Helfer')!

Ich bin mir sicher, dass Sie den im obigen Artikel vorgeschlagenen Ansatz ein wenig mit anonymen Delegierten wie Func<T> oder Predicate<T> anreichern können und das Schreiben von benutzerdefinierten Codeblöcken für die Validierungsregeln aktiviert übergreifende Bedingungen (z. B. die von Ihnen genannte Bedingung wo, wenn Ihre ShippingSameAsBilling Eigenschaft wahr ist, dann können Sie mehr Regeln für die Lieferadresse, usw. ignorieren).

DataAnnotations dient dazu, einfache Validierungsregeln für Objekte wirklich einfach mit sehr wenig Code zu machen. Aber wenn sich Ihre Anforderungen entwickeln, müssen Sie komplexere Regeln validieren. Die neuen virtuellen Methoden im MVC2-Modellbinder sollten uns weiterhin Möglichkeiten bieten, unsere zukünftigen Validierungserfindungen in das MVC-Framework zu integrieren.

2

Stellen Sie sicher, dass die Felder, die nicht validiert werden sollen, nicht in die Aktion eingegeben werden. Wir validieren nur die Felder, die tatsächlich gebucht wurden.

Edit: (von Frages)

Dieses Verhalten wird in MVC2 RC2 geändert:

Standardvalidierungssystem validiert gesamtes Modell Der Standard Validierung System in ASP.NET MVC 1.0 und in Vorschau von ASP.NET MVC 2 vor RC 2 validierte nur Modelleigenschaften, die wurden auf dem Server veröffentlicht. In ASP.NET MVC 2 besteht das neue Verhalten darin, dass alle Modelleigenschaften validiert werden, wenn das Modell validiert wird, unabhängig davon, ob ein neuer Wert veröffentlicht wurde. Anwendungen, die auf dem ASP.NET MVC 1.0-Verhalten beruhen, erfordern möglicherweise Änderungen. Für weitere Informationen über diese Änderung finden Sie den Eintrag Input Validation vs. Model Validation in ASP.NET MVC auf Brad Wilson Blog.

0

Dies ist nicht auf DataAnnotations verwandt, aber haben Sie sich das Projekt Fluent Validation? Es gibt Ihnen eine genaue Kontrolle über Ihre Validierung und wenn Sie Objekt-zu-Objekt-Validierung haben, wird ein Aggregat-Objekt der beiden Objekte Sie in Gang bringen.

Auch scheint es mit MVC im Auge zu haben, bauen gewesen, aber es hat auch seine eigene „Laufzeit“, so dass Sie es in anderen .NET-Anwendungen verwenden können, auch das ist ein weiterer Bonus in meinem Buch ist.

1

Für die komplexeren Fälle habe ich von einfachen DataAnnotations zu den folgenden verschoben: Validation with visitors and extension methods.

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

mit einer Methode eine Eigenschaft mit Namen über DataAnnotations zu bestätigen (die ich nicht atm haben):

Wenn Sie von Ihrem DataAnnotations machen wollen würden Sie so etwas wie die folgenden ersetzen.

1

Ich habe eine Teilmodellmappe erstellt, die nur die übermittelten Schlüssel validiert. Aus Sicherheitsgründen (wenn ich noch einen Schritt weiter gehen würde) würde ich ein Datenanmerkungsattribut erstellen, das angibt, welche Felder von einem Modell ausgeschlossen werden dürfen.Dann überprüft OnModelUpdated die Feldattribute, um sicherzustellen, dass keine unerwünschte Unterlagerung erfolgt.

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