ASP.NET MVC Bedingte Validierung

129

Wie verwende ich Datenanmerkungen, um eine bedingte Validierung des Modells durchzuführen?

Nehmen wir zum Beispiel an, wir haben das folgende Modell (Person und 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;
    }
}

Und die folgende Ansicht:

<%= 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)%>

Ich möchte das bedingte Pflichtfeld "Senior.Description" sein, das auf der Auswahl der Eigenschaft "IsSenior" basiert (true -> erforderlich). Wie implementiere ich die bedingte Validierung in ASP.NET MVC 2 mit Datenanmerkungen?

Peter Stegnar
quelle
1
Ich habe kürzlich eine ähnliche Frage gestellt: stackoverflow.com/questions/2280539/…
Darin Dimitrov
Ich bin verwirrt. Ein SeniorObjekt ist immer ein Senior. Warum kann IsSenior in diesem Fall falsch sein? Muss die Eigenschaft 'Person.Senior' nicht einfach null sein, wenn sie Person.IsSeniorfalsch ist ? Oder warum nicht die IsSeniorEigenschaft wie folgt implementieren : bool IsSenior { get { return this.Senior != null; } }.
Steven
Steven: "IsSenior" wird in das Kontrollkästchen in der Ansicht übersetzt. Wenn der Benutzer das Kontrollkästchen "IsSenior" aktiviert, wird das Feld "Senior.Description" obligatorisch.
Peter Stegnar
Darin Dimitrov: Na ja, aber nicht ganz. Sie sehen, wie würden Sie erreichen, dass die Fehlermeldung an das spezifische Feld angehängt wird? Wenn Sie auf Objektebene validieren, wird auf Objektebene ein Fehler angezeigt. Ich brauche Fehler auf Eigenschaftsebene.
Peter Stegnar

Antworten:

150

Es gibt eine viel bessere Möglichkeit, bedingte Validierungsregeln in MVC3 hinzuzufügen. Lassen Sie Ihr Modell IValidatableObjectdie ValidateMethode erben und implementieren :

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

Weitere Informationen finden Sie unter Einführung in ASP.NET MVC 3 (Vorschau 1) .

Viperguynaz
quelle
Wenn die Eigenschaft vom Typ "int" ist, ist ein Wert erforderlich. Wenn dieses Feld ausgefüllt wird, funktioniert Validieren nicht.
Jeyhun Rahimov
2
Leider hat Microsoft dies in die falsche Ebene verschoben - die Validierung ist Geschäftslogik und diese Schnittstelle befindet sich in der System.Web-DLL. Um dies nutzen zu können, müssen Sie Ihrer Business-Schicht eine Abhängigkeit von einer Präsentationstechnologie geben.
NightOwl888
7
Sie tun, wenn Sie es implementieren - siehe vollständiges Beispiel unter falconwebtech.com/post/…
viperguynaz
4
falconwebtech.com/post/… - @viperguynaz das funktioniert nicht
Smit Patel
1
@ RayLoveless sollten Sie anrufen ModelState.IsValid- nicht direkt Validate anrufen
viperguynaz
63

Ich habe dies durch die Behandlung des "ModelState" gelöst. -Wörterbuch behandelt habe, das in der Steuerung enthalten ist. Das ModelState-Wörterbuch enthält alle Mitglieder, die validiert werden müssen.

Hier ist die Lösung:

Wenn Sie eine bedingte Validierung basierend auf einem Feld implementieren müssen (z. B. wenn A = true, ist B erforderlich), während Sie Fehlermeldungen auf Eigenschaftsebene beibehalten (dies gilt nicht für benutzerdefinierte Validatoren auf Objektebene), können Sie dies erreichen indem Sie "ModelState" behandeln, indem Sie einfach unerwünschte Validierungen daraus entfernen.

... In einer Klasse ...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

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

... Klasse geht weiter ...

... In einer Controller-Aktion ...

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

...

Damit erreichen wir eine bedingte Validierung, während alles andere gleich bleibt.


AKTUALISIEREN:

Dies ist meine endgültige Implementierung: Ich habe eine Schnittstelle für das Modell und das Aktionsattribut verwendet, das das Modell validiert, das diese Schnittstelle implementiert. Die Schnittstelle schreibt die Validate-Methode (ModelStateDictionary modelState) vor. Das Attribut on action ruft nur Validate (modelState) für IValidatorSomething auf.

Ich wollte diese Antwort nicht komplizieren, deshalb habe ich die endgültigen Implementierungsdetails (die am Ende im Produktionscode eine Rolle spielen) nicht erwähnt.

Peter Stegnar
quelle
17
Der Nachteil ist, dass sich ein Teil Ihrer Validierungslogik im Modell und der andere Teil in den Controllern befindet.
Kristof Claes
Das ist natürlich nicht nötig. Ich zeige nur das grundlegendste Beispiel. Ich habe dies mit der Schnittstelle auf dem Modell und mit dem Aktionsattribut implementiert, das das Modell validiert, das die erwähnte Schnittstelle implementiert. Die Schnittstelle schwitzt die Validate-Methode (ModelStateDictionary modelState). Schließlich führen Sie alle Validierungen im Modell durch. Wie auch immer, guter Punkt.
Peter Stegnar
Ich mag die Einfachheit dieses Ansatzes in der Zwischenzeit, bis das MVC-Team etwas Besseres aus der Box baut. Aber funktioniert Ihre Lösung mit aktivierter clientseitiger Validierung?
Aaron
2
@ Aaron: Ich bin froh, dass Ihnen die Lösung gefällt, aber leider funktioniert diese Lösung nicht mit clientseitiger Validierung (da jedes Validierungsattribut seine JavaScript-Implementierung benötigt). Sie können sich mit dem Attribut "Remote" helfen, sodass nur ein Ajax-Aufruf zur Validierung ausgegeben wird.
Peter Stegnar
Können Sie diese Antwort erweitern? Das macht Sinn, aber ich möchte sicherstellen, dass ich kristallklar bin. Ich bin mit genau dieser Situation konfrontiert und möchte, dass sie gelöst wird.
Richard B
36

Ich hatte gestern das gleiche Problem, aber ich habe es auf sehr saubere Weise gemacht, was sowohl für die clientseitige als auch für die serverseitige Validierung funktioniert.

Bedingung: Basierend auf dem Wert einer anderen Eigenschaft im Modell möchten Sie eine andere Eigenschaft erforderlich machen. Hier ist der 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;
    }
}

Hier ist PropertyName die Eigenschaft, für die Sie Ihre Bedingung festlegen möchten. DesiredValue ist der bestimmte Wert des PropertyName (der Eigenschaft), für den Ihre andere Eigenschaft für erforderlich validiert werden muss

Angenommen, Sie haben Folgendes

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

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

Registrieren Sie zu guter Letzt den Adapter für Ihr Attribut, damit es eine clientseitige Validierung durchführen kann (ich habe ihn in global.asax, Application_Start eingefügt).

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
Dan Hunex
quelle
Dies war der ursprüngliche Ausgangspunkt miroprocessordev.blogspot.com/2012/08/…
Dan Hunex
Gibt es eine gleichwertige Lösung in asp.net mvc2? ValidationResult, ValidationContext-Klassen sind in asp.net mvc2 (.net Framework 3.5) nicht verfügbar
User_MVC
2
Dies funktioniert nur serverseitig, wie im verlinkten Blog angegeben
Pakman
2
Ich habe es geschafft, dies auf der Clientseite mit MVC5 zum Laufen zu bringen, aber im Client wird die Validierung ausgelöst, unabhängig davon, um welchen DesiredValue es sich handelt.
Geethanga
1
@ Dan Hunex: In MVC4 habe ich es nicht geschafft, auf Client-Seite ordnungsgemäß zu arbeiten, und es wird die Validierung ausgelöst, unabhängig davon, um welchen DesiredValue es sich handelt. Irgendwelche Hilfe bitte?
Jack
34

Ich habe dieses erstaunliche Nuget verwendet, das dynamische Anmerkungen ExpressiveAnnotations ausführt

Sie können jede Logik validieren, von der Sie träumen können:

public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
Korayem
quelle
3
Die ExpressiveAnnotation-Bibliothek ist die flexibelste und allgemeinste Lösung aller Antworten. Danke für das Teilen!
Sudhanshu Mishra
2
Ich habe mir den Kopf geschlagen, um eine Lösung für einen soliden Tag zu finden. ExpressiveAnnotations scheint die Lösung für mich zu sein!
Caverman
ExpressiveAnnotation Bibliothek ist fantastisch!
Doug Knudsen
1
Es hat auch clientseitigen Support!
Nattrass
1
Keine Unterstützung für .NET Core und es sieht nicht so aus, als würde es passieren.
Gosr
18

Sie können Validatoren bedingt deaktivieren, indem Sie Fehler aus ModelState entfernen:

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

Es gibt jetzt ein Framework, das diese bedingte Validierung (neben anderen praktischen Validierungen von Datenanmerkungen) sofort durchführt: http://foolproof.codeplex.com/

Schauen Sie sich insbesondere den Validator [RequiredIfTrue ("IsSenior")] an. Sie platzieren dies direkt in der Eigenschaft, die Sie validieren möchten, damit Sie das gewünschte Verhalten des Validierungsfehlers erhalten, der der Eigenschaft "Senior" zugeordnet ist.

Es ist als NuGet-Paket erhältlich.

bojingo
quelle
3

Sie müssen auf Personenebene validieren, nicht auf Seniorenebene, oder Senior muss einen Verweis auf die übergeordnete Person haben. Es scheint mir, dass Sie einen Selbstvalidierungsmechanismus benötigen, der die Validierung für die Person und nicht für eine ihrer Eigenschaften definiert. Ich bin mir nicht sicher, aber ich glaube nicht, dass DataAnnotations dies sofort unterstützt. Was Sie tun können, um Ihre eigenen zu erstellen Attribute, die daraus abgeleitet werden ValidationAttribute, kann auf Klassenebene dekoriert werden. Als Nächstes erstellen Sie einen benutzerdefinierten Validator, mit dem auch diese Validatoren auf Klassenebene ausgeführt werden können.

Ich weiß, dass Validation Application Block die Selbstvalidierung sofort unterstützt, aber VAB hat eine ziemlich steile Lernkurve. Hier ist jedoch ein Beispiel für die Verwendung von 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));
        }
    }
}
Steven
quelle
"Sie müssen auf Personenebene validieren, nicht auf Senior-Ebene" Ja, dies ist eine Option, aber Sie verlieren die Fähigkeit, dass der Fehler an ein bestimmtes Feld angehängt wird, das im Senior-Objekt erforderlich ist.
Peter Stegnar
3

Ich hatte das gleiche Problem, brauchte eine Änderung des Attributs [Erforderlich] - Feld erforderlich in Abhängigkeit von der http-Anfrage. Die Lösung ähnelte der Antwort von Dan Hunex, aber seine Lösung funktionierte nicht richtig (siehe Kommentare). Ich verwende keine unauffällige Validierung, sondern nur MicrosoftMvcValidation.js. Hier ist es. Implementieren Sie Ihr benutzerdefiniertes Attribut:

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
    }


}

Dann müssen Sie Ihren benutzerdefinierten Anbieter implementieren, um ihn als Adapter in Ihrer global.asax zu verwenden

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

Und ändern Sie Ihre global.asax mit einer Linie

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

und hier ist es

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

Der Hauptvorteil für mich ist, dass ich keinen benutzerdefinierten Client-Validator wie im Fall einer unauffälligen Validierung codieren muss. Es funktioniert genauso wie [Erforderlich], jedoch nur in den von Ihnen gewünschten Fällen.

Den
quelle
Der Teil über das Erweitern DataAnnotationsModelValidatorwar genau das, was ich sehen musste. Danke dir.
Twip
2

Schauen Sie sich die bedingte Validierung von Simon Ince in MVC an .

Ich arbeite gerade an seinem Beispielprojekt.

Merritt
quelle
0

Typische Verwendung zur bedingten Beseitigung von Fehlern aus dem Modellstatus:

  1. Machen Sie den ersten Teil der Controller-Aktion bedingt
  2. Führen Sie eine Logik aus, um Fehler aus ModelState zu entfernen
  3. Führen Sie den Rest der vorhandenen Logik aus (normalerweise die Validierung des Modellstatus, dann alles andere).

Beispiel:

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

Behalten Sie in Ihrem Beispiel alles bei und fügen Sie die vorgeschlagene Logik zur Aktion Ihres Controllers hinzu. Ich gehe davon aus, dass in Ihrem an die Controller-Aktion übergebenen ViewModel die Objekte Person und Senior Person mit Daten aus der Benutzeroberfläche gefüllt sind.

Jeremy Ray Brown
quelle
0

Ich verwende MVC 5, aber Sie können Folgendes ausprobieren:

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

In Ihrem Fall würden Sie so etwas wie "IsSenior == true" sagen. Dann müssen Sie nur noch die Validierung Ihrer Post-Aktion überprüfen.


quelle