RequiredIfContains

Das RequiredIfContainsAttribute macht ein Feld zum Pflichtfeld, sobald eine andere Eigenschaft einen von mehreren Werten hat. Es ist damit das ODER-Gegenstück zu RequiredIf, das genau einen Zielwert je Bedingung prüft.

Ausgeblendet statt gesperrt (HideIfNotRequired)

Die Kontaktangabe erscheint erst, sobald eine Rückmeldung gewünscht ist – und ist dann Pflicht.

@model RequiredIfContainsAusblendenBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIfContains" asp-action="Ausblenden" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Rueckmeldeart)
    @Html.EditorFor(m => m.Kontaktangabe)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class RequiredIfContainsAusblendenBeispielViewModel
    {
        public IEnumerable<SelectListItem> Rueckmeldearten { get; } = new List<SelectListItem>
                                                                     {
                                                                         new ("Keine Rückmeldung", "Keine"),
                                                                         new ("Per E-Mail", "Mail"),
                                                                         new ("Per Telefon", "Telefon"),
                                                                     };

        [Display(Name = "Rückmeldung")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Rueckmeldearten))]
        public string Rueckmeldeart { get; set; }

        [Display(Name = "Kontaktangabe")]
        [UIHint("String")]
        [RequiredIfContains(
            nameof(Rueckmeldeart),
            "Mail",
            "Telefon",
            HideIfNotRequired = true,
            ErrorMessage = "Bitte die Kontaktangabe erfassen, wenn eine Rückmeldung gewünscht ist.")]
        public string Kontaktangabe { get; set; }
    }
}
using System.Collections.Generic;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.Basis.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Basis.Controllers.Beispiele
{
    [Area("Basis")]
    public class RequiredIfContainsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfContainsBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfContainsBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);

                // PRG-Pattern (Post-Redirect-Get): nach erfolgreichem POST per Redirect auf die
                // GET-Action weiterleiten, damit ein erneutes Laden/Aktualisieren das Formular nicht
                // nochmals abschickt (verhindert doppeltes Speichern).
                return this.RedirectToAction(nameof(this.Index));
            }

            // Ungültige Eingabe: dieselbe Seite erneut anzeigen, damit die Validierungsfehler erscheinen.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult CheckboxList()
        {
            return this.View(new RequiredIfContainsCheckboxListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckboxList(RequiredIfContainsCheckboxListBeispielViewModel modell)
        {
            // Sicherheitsnetz für den Fehlerfall: Wird die View mit den Validierungsfehlern erneut
            // gerendert, muss die Collection gesetzt sein – das CheckboxList-Template prüft nicht auf null.
            // Ohne Häkchen schickt der Browser gar keinen Wert für die Liste mit.
            modell.Verpflegung ??= new List<int>();

            if (this.ModelState.IsValid)
            {
                // Gültig: es ist weder Allergiker- noch Sonderkost angehakt oder die Angaben zur Kost
                // sind erfasst. Die Bedingung greift, sobald *ein* angehakter Wert ein Zielwert ist.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.CheckboxList));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textbox()
        {
            return this.View(new RequiredIfContainsTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfContainsTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: die Zahlungsart steht auf keinem der Zielwerte oder die Referenznummer ist erfasst.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Dropdown()
        {
            return this.View(new RequiredIfContainsDropdownBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfContainsDropdownBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Dropdown));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Zielwerte()
        {
            return this.View(new RequiredIfContainsZielwerteBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Zielwerte(RequiredIfContainsZielwerteBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Die Zielwerte liest das Attribut bei der Prüfung aus LaenderMitMwstNummer –
                // das gebundene Modell trägt die Liste also mit in die Validierung.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Zielwerte));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Ausblenden()
        {
            return this.View(new RequiredIfContainsAusblendenBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ausblenden(RequiredIfContainsAusblendenBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // HideIfNotRequired wirkt nur clientseitig: das Feld wird trotzdem mitgeschickt
                // und serverseitig geprüft.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Ausblenden));
            }

            return this.View(modell);
        }
    }
}

Darstellung

HideIfNotRequired = true blendet das Feld aus, solange die Bedingung nicht zutrifft. Das Formular bleibt dadurch kurz und zeigt nur, was gerade gebraucht wird — hier erscheint die Kontaktangabe erst, wenn überhaupt eine Rückmeldung gewünscht ist.

[Display(Name = "Kontaktangabe")]
[UIHint("String")]
[RequiredIfContains(
    nameof(Rueckmeldeart),
    "Mail",
    "Telefon",
    HideIfNotRequired = true,
    ErrorMessage = "…")]
public string Kontaktangabe { get; set; }

Optionen

Option Wirkung
[RequiredIfContains(nameof(Feld), "A", "B")] Pflicht, sobald Feld einen der Zielwerte hat (ODER)
TargetValuesProperty = nameof(Liste) Zielwerte aus einer Modell-Eigenschaft statt als Literale
DisableIfNotRequired = true Feld ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Feld ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar
AllowEmptyStrings = true Von [Required] geerbt: ein leerer String gilt dann als ausgefüllt

Ausblenden oder sperren?

  • HideIfNotRequired versteckt die Feldgruppe. Das Feld bleibt im Formular und wird mitgeschickt — ein bereits erfasster Wert überlebt das Umschalten und kommt beim Server an.
  • DisableIfNotRequired sperrt das Eingabefeld. Gesperrte Felder schickt der Browser nicht mit, ein erfasster Wert geht beim Umschalten also verloren. Sperren eignet sich, wenn der Zusammenhang sichtbar bleiben soll; Ausblenden, wenn das Feld sonst nur stört.
  • Beides wirkt rein clientseitig. Ohne JavaScript ist das Feld sichtbar und bedienbar; die eigentliche Prüfung macht in jedem Fall der Server.
  • Wird die Bedingung durch eine Eingabe wahr, setzt die Logik zusätzlich den Fokus in das eingeblendete Feld.

CheckboxList als Ausgangsfeld (UIHint "CheckboxList")

Mehrfachauswahl als Bedingung: die Angaben zur Kost werden Pflicht, sobald „Allergikerkost“ oder „Sonderkost“ angehakt ist.

@model RequiredIfContainsCheckboxListBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIfContains" asp-action="CheckboxList" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Verpflegung)
    @Html.EditorFor(m => m.Kostangaben)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Controls;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class RequiredIfContainsCheckboxListBeispielViewModel
    {
        // Verglichen wird der Value des Eintrags, nicht sein Anzeigetext. Als Konstanten stehen
        // Auswahlliste und Bedingung nebeneinander und laufen nicht auseinander.
        private const string MahlzeitAllergikerkost = "3";

        private const string MahlzeitSonderkost = "4";

        public IEnumerable<SelectListItem> Mahlzeiten { get; } = new List<SelectListItem>
                                                                 {
                                                                     new ("Mittagessen", "1"),
                                                                     new ("Abendessen", "2"),
                                                                     new ("Allergikerkost", MahlzeitAllergikerkost),
                                                                     new ("Sonderkost", MahlzeitSonderkost),
                                                                 };

        [Display(Name = "Verpflegung")]
        [UIHint("CheckboxList")]
        [AdditionalMetadata("Selection", nameof(Mahlzeiten))]
        [AdditionalMetadata("Direction", FlexDirection.Row)]
        public IEnumerable<int> Verpflegung { get; set; } = new List<int>();

        [Display(Name = "Angaben zur Kost")]
        [UIHint("String")]
        [RequiredIfContains(
            nameof(Verpflegung),
            MahlzeitAllergikerkost,
            MahlzeitSonderkost,
            ErrorMessage = "Bitte die Angaben zur Kost erfassen, wenn Allergiker- oder Sonderkost gewählt ist.")]
        public string Kostangaben { get; set; }
    }
}
using System.Collections.Generic;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.Basis.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Basis.Controllers.Beispiele
{
    [Area("Basis")]
    public class RequiredIfContainsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfContainsBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfContainsBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);

                // PRG-Pattern (Post-Redirect-Get): nach erfolgreichem POST per Redirect auf die
                // GET-Action weiterleiten, damit ein erneutes Laden/Aktualisieren das Formular nicht
                // nochmals abschickt (verhindert doppeltes Speichern).
                return this.RedirectToAction(nameof(this.Index));
            }

            // Ungültige Eingabe: dieselbe Seite erneut anzeigen, damit die Validierungsfehler erscheinen.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult CheckboxList()
        {
            return this.View(new RequiredIfContainsCheckboxListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckboxList(RequiredIfContainsCheckboxListBeispielViewModel modell)
        {
            // Sicherheitsnetz für den Fehlerfall: Wird die View mit den Validierungsfehlern erneut
            // gerendert, muss die Collection gesetzt sein – das CheckboxList-Template prüft nicht auf null.
            // Ohne Häkchen schickt der Browser gar keinen Wert für die Liste mit.
            modell.Verpflegung ??= new List<int>();

            if (this.ModelState.IsValid)
            {
                // Gültig: es ist weder Allergiker- noch Sonderkost angehakt oder die Angaben zur Kost
                // sind erfasst. Die Bedingung greift, sobald *ein* angehakter Wert ein Zielwert ist.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.CheckboxList));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textbox()
        {
            return this.View(new RequiredIfContainsTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfContainsTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: die Zahlungsart steht auf keinem der Zielwerte oder die Referenznummer ist erfasst.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Dropdown()
        {
            return this.View(new RequiredIfContainsDropdownBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfContainsDropdownBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Dropdown));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Zielwerte()
        {
            return this.View(new RequiredIfContainsZielwerteBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Zielwerte(RequiredIfContainsZielwerteBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Die Zielwerte liest das Attribut bei der Prüfung aus LaenderMitMwstNummer –
                // das gebundene Modell trägt die Liste also mit in die Validierung.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Zielwerte));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Ausblenden()
        {
            return this.View(new RequiredIfContainsAusblendenBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ausblenden(RequiredIfContainsAusblendenBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // HideIfNotRequired wirkt nur clientseitig: das Feld wird trotzdem mitgeschickt
                // und serverseitig geprüft.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Ausblenden));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Eine CheckboxList eignet sich als Ausgangsfeld: Das Attribut wertet die Mehrfachauswahl aus und macht das abhängige Feld zur Pflicht, sobald ein angehaktes Element einem der Zielwerte entspricht (ODER). Das Attribut sitzt dabei am abhängigen Feld — hier an der Textbox —, nicht an der Liste.

private const string MahlzeitAllergikerkost = "3";
private const string MahlzeitSonderkost = "4";

[Display(Name = "Verpflegung")]
[UIHint("CheckboxList")]
[AdditionalMetadata("Selection", nameof(Mahlzeiten))]
public IEnumerable<int> Verpflegung { get; set; } = new List<int>();

[Display(Name = "Angaben zur Kost")]
[UIHint("String")]
[RequiredIfContains(nameof(Verpflegung), MahlzeitAllergikerkost, MahlzeitSonderkost, ErrorMessage = "…")]
public string Kostangaben { get; set; }

Die Liste darf nie null sein. Das CheckboxList-Template greift ungeprüft auf die Collection zu und bricht sonst mit einer ArgumentNullException ab. Ohne ein einziges Häkchen schickt der Browser gar keinen Wert für die Liste — deshalb der leere Initialisierer und die zusätzliche Absicherung im POST, bevor die Fehlerseite erneut gerendert wird.

Verglichen wird der Value, nicht der Anzeigetext

Die CheckboxList bindet an IEnumerable<int>, die Zielwerte im Attribut sind Strings — verglichen wird über ToString(), also "3" gegen 3. Die Zielwerte sind damit die Value-Angaben der SelectListItems und nicht ihre Beschriftung. Weil diese Werte an zwei Stellen stehen (Auswahlliste und Attribut), hält sie das Beispiel als Konstanten zusammen; ein direkt hingeschriebenes "3" läuft beim ersten Umsortieren der Liste stillschweigend ins Leere.

Client- und Serverseite

  • Server: IsRequired(...) erkennt eine Collection als Ausgangswert und prüft jeden Eintrag gegen die Zielwerte — ein Treffer genügt. Ein string zählt dabei als Einzelwert, nicht als Zeichen-Collection.
  • Client: Der Adapter liest alle angehakten Checkboxen der gleichnamigen Gruppe. Jedes Setzen und Entfernen eines Häkchens bewertet die Bedingung neu, weil dafür immer die gesamte Auswahl gelesen wird und nicht nur der zuletzt geänderte Eintrag.
  • Beide Seiten kommen damit zum selben Ergebnis; die Textbox rendert die bedingten data-val-requiredifcontains-*-Attribute.

Optionen

Option Wirkung
[RequiredIfContains(nameof(Liste), "3", "4")] Pflicht, sobald die Mehrfachauswahl einen der Zielwerte enthält
TargetValuesProperty = nameof(Zielwerte) Zielwerte aus einer Modell-Eigenschaft statt als Literale
DisableIfNotRequired = true Abhängiges Feld ist gesperrt, solange kein Zielwert angehakt ist
HideIfNotRequired = true Abhängiges Feld ist ausgeblendet, solange kein Zielwert angehakt ist
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar
[AdditionalMetadata("Direction", FlexDirection.Row)] Häkchen nebeneinander statt untereinander

Weitere Hinweise

  • Die Bedingung ist ein ODER über die Zielwerte. Eine Bedingung „beide Häkchen gesetzt“ leistet das Attribut nicht; dafür braucht es eine eigene Validierung (IValidatableObject).
  • Genauso funktioniert eine MultiselectList als Ausgangsfeld — auch sie liefert mehrere Werte.
  • Umgekehrt darf eine CheckboxList das Attribut auch selbst tragen: Eine leere Collection gilt als nicht ausgefüllt, aus dem Attribut wird dann eine bedingte «mindestens ein Häkchen»-Regel. Die Bedingung prüft in dieser Richtung allerdings nur der Server, weil das CheckboxList-Template keine bedingten Client-Attribute rendert (siehe Abschnitt „Textbox“).

DropdownList (UIHint "DropdownList")

Auf einem Auswahlfeld: die Einsatzdauer wird bei den Vertragsarten „Temporär“ und „Praktikum“ zur Pflicht.

@model RequiredIfContainsDropdownBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIfContains" asp-action="Dropdown" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Vertragsart)
    @Html.EditorFor(m => m.Einsatzdauer)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class RequiredIfContainsDropdownBeispielViewModel
    {
        public IEnumerable<SelectListItem> Vertragsarten { get; } = new List<SelectListItem>
                                                                   {
                                                                       new ("Festanstellung", "Fest"),
                                                                       new ("Temporär", "Temporaer"),
                                                                       new ("Praktikum", "Praktikum"),
                                                                   };

        public IEnumerable<SelectListItem> Einsatzdauern { get; } = new List<SelectListItem>
                                                                   {
                                                                       new ("bis 3 Monate", "3"),
                                                                       new ("bis 6 Monate", "6"),
                                                                       new ("bis 12 Monate", "12"),
                                                                   };

        [Display(Name = "Vertragsart")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Vertragsarten))]
        public string Vertragsart { get; set; }

        [Display(Name = "Einsatzdauer")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Einsatzdauern))]
        [RequiredIfContains(nameof(Vertragsart), "Temporaer", "Praktikum", ErrorMessage = "Bitte die Einsatzdauer wählen, wenn die Vertragsart „Temporär“ oder „Praktikum“ ist.")]
        public string Einsatzdauer { get; set; }
    }
}
using System.Collections.Generic;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.Basis.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Basis.Controllers.Beispiele
{
    [Area("Basis")]
    public class RequiredIfContainsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfContainsBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfContainsBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);

                // PRG-Pattern (Post-Redirect-Get): nach erfolgreichem POST per Redirect auf die
                // GET-Action weiterleiten, damit ein erneutes Laden/Aktualisieren das Formular nicht
                // nochmals abschickt (verhindert doppeltes Speichern).
                return this.RedirectToAction(nameof(this.Index));
            }

            // Ungültige Eingabe: dieselbe Seite erneut anzeigen, damit die Validierungsfehler erscheinen.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult CheckboxList()
        {
            return this.View(new RequiredIfContainsCheckboxListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckboxList(RequiredIfContainsCheckboxListBeispielViewModel modell)
        {
            // Sicherheitsnetz für den Fehlerfall: Wird die View mit den Validierungsfehlern erneut
            // gerendert, muss die Collection gesetzt sein – das CheckboxList-Template prüft nicht auf null.
            // Ohne Häkchen schickt der Browser gar keinen Wert für die Liste mit.
            modell.Verpflegung ??= new List<int>();

            if (this.ModelState.IsValid)
            {
                // Gültig: es ist weder Allergiker- noch Sonderkost angehakt oder die Angaben zur Kost
                // sind erfasst. Die Bedingung greift, sobald *ein* angehakter Wert ein Zielwert ist.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.CheckboxList));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textbox()
        {
            return this.View(new RequiredIfContainsTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfContainsTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: die Zahlungsart steht auf keinem der Zielwerte oder die Referenznummer ist erfasst.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Dropdown()
        {
            return this.View(new RequiredIfContainsDropdownBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfContainsDropdownBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Dropdown));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Zielwerte()
        {
            return this.View(new RequiredIfContainsZielwerteBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Zielwerte(RequiredIfContainsZielwerteBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Die Zielwerte liest das Attribut bei der Prüfung aus LaenderMitMwstNummer –
                // das gebundene Modell trägt die Liste also mit in die Validierung.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Zielwerte));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Ausblenden()
        {
            return this.View(new RequiredIfContainsAusblendenBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ausblenden(RequiredIfContainsAusblendenBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // HideIfNotRequired wirkt nur clientseitig: das Feld wird trotzdem mitgeschickt
                // und serverseitig geprüft.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Ausblenden));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Auf einem Auswahlfeld zählt der leere Platzhalter-Eintrag als «nicht ausgefüllt» — das Feld ist also nur dann gültig leer, wenn keiner der Zielwerte gewählt ist. Verglichen wird der Value des Eintrags im Ausgangsfeld ("Temporaer"), nicht sein Anzeigetext ("Temporär").

[Display(Name = "Einsatzdauer")]
[UIHint("DropdownList")]
[AdditionalMetadata("Selection", nameof(Einsatzdauern))]
[RequiredIfContains(nameof(Vertragsart), "Temporaer", "Praktikum", ErrorMessage = "…")]
public string Einsatzdauer { get; set; }

Optionen

Option Wirkung
[RequiredIfContains(nameof(Feld), "A", "B")] Pflicht, sobald Feld einen der Zielwerte hat (ODER)
TargetValuesProperty = nameof(Liste) Zielwerte aus einer Modell-Eigenschaft statt als Literale
DisableIfNotRequired = true Auswahlfeld ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Auswahlfeld ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar
[EmptyEntry(false)] Unterdrückt den leeren Platzhalter — siehe Hinweis unten

Besonderheiten dieses Templates

  • Wird der leere Eintrag über [EmptyEntry(false)] unterdrückt, ist immer ein Wert gewählt und die Bedingung kann nie „nicht ausgefüllt“ feststellen.
  • Ein Auswahlfeld eignet sich ebenso als Ausgangsfeld. Das ist sogar der Normalfall: Eine feste Werteliste im Attribut passt naturgemäss zu einem Feld mit fester Auswahl.
  • Trifft nur ein Zielwert in Frage, ist [RequiredIf(nameof(Vertragsart), "Temporaer")] das schlankere Attribut — RequiredIfContains lohnt sich ab dem zweiten Wert.

Textbox (UIHint "String")

Der Grundfall: die Referenznummer wird bei den Zahlungsarten „Rechnung“ und „Kreditkarte“ zur Pflicht.

@model RequiredIfContainsTextboxBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIfContains" asp-action="Textbox" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Zahlungsart)
    @Html.EditorFor(m => m.Referenznummer)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class RequiredIfContainsTextboxBeispielViewModel
    {
        public IEnumerable<SelectListItem> Zahlungsarten { get; } = new List<SelectListItem>
                                                                   {
                                                                       new ("Bar", "Bar"),
                                                                       new ("Rechnung", "Rechnung"),
                                                                       new ("Kreditkarte", "Kreditkarte"),
                                                                   };

        [Display(Name = "Zahlungsart")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Zahlungsarten))]
        public string Zahlungsart { get; set; }

        [Display(Name = "Referenznummer")]
        [UIHint("String")]
        [RequiredIfContains(nameof(Zahlungsart), "Rechnung", "Kreditkarte", ErrorMessage = "Bitte die Referenznummer angeben, wenn als Zahlungsart „Rechnung“ oder „Kreditkarte“ gewählt ist.")]
        public string Referenznummer { get; set; }
    }
}
using System.Collections.Generic;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.Basis.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Basis.Controllers.Beispiele
{
    [Area("Basis")]
    public class RequiredIfContainsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfContainsBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfContainsBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);

                // PRG-Pattern (Post-Redirect-Get): nach erfolgreichem POST per Redirect auf die
                // GET-Action weiterleiten, damit ein erneutes Laden/Aktualisieren das Formular nicht
                // nochmals abschickt (verhindert doppeltes Speichern).
                return this.RedirectToAction(nameof(this.Index));
            }

            // Ungültige Eingabe: dieselbe Seite erneut anzeigen, damit die Validierungsfehler erscheinen.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult CheckboxList()
        {
            return this.View(new RequiredIfContainsCheckboxListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckboxList(RequiredIfContainsCheckboxListBeispielViewModel modell)
        {
            // Sicherheitsnetz für den Fehlerfall: Wird die View mit den Validierungsfehlern erneut
            // gerendert, muss die Collection gesetzt sein – das CheckboxList-Template prüft nicht auf null.
            // Ohne Häkchen schickt der Browser gar keinen Wert für die Liste mit.
            modell.Verpflegung ??= new List<int>();

            if (this.ModelState.IsValid)
            {
                // Gültig: es ist weder Allergiker- noch Sonderkost angehakt oder die Angaben zur Kost
                // sind erfasst. Die Bedingung greift, sobald *ein* angehakter Wert ein Zielwert ist.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.CheckboxList));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textbox()
        {
            return this.View(new RequiredIfContainsTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfContainsTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: die Zahlungsart steht auf keinem der Zielwerte oder die Referenznummer ist erfasst.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Dropdown()
        {
            return this.View(new RequiredIfContainsDropdownBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfContainsDropdownBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Dropdown));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Zielwerte()
        {
            return this.View(new RequiredIfContainsZielwerteBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Zielwerte(RequiredIfContainsZielwerteBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Die Zielwerte liest das Attribut bei der Prüfung aus LaenderMitMwstNummer –
                // das gebundene Modell trägt die Liste also mit in die Validierung.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Zielwerte));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Ausblenden()
        {
            return this.View(new RequiredIfContainsAusblendenBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ausblenden(RequiredIfContainsAusblendenBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // HideIfNotRequired wirkt nur clientseitig: das Feld wird trotzdem mitgeschickt
                // und serverseitig geprüft.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Ausblenden));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Das RequiredIfContainsAttribute erbt von RequiredAttribute und macht ein Feld zum Pflichtfeld, sobald eine andere Eigenschaft einen von mehreren Werten hat. Es ist damit das ODER-Gegenstück zu RequiredIf, das je Bedingung genau einen Zielwert prüft. Dieser Abschnitt beschreibt das Attribut vollständig; die übrigen Beispiele zeigen die einzelnen Optionen in Aktion.

[Display(Name = "Zahlungsart")]
[UIHint("DropdownList")]
[AdditionalMetadata("Selection", nameof(Zahlungsarten))]
public string Zahlungsart { get; set; }

[Display(Name = "Referenznummer")]
[UIHint("String")]
[RequiredIfContains(nameof(Zahlungsart), "Rechnung", "Kreditkarte", ErrorMessage = "…")]
public string Referenznummer { get; set; }

Optionen

Option Wirkung
[RequiredIfContains(nameof(Feld), "A", "B")] Pflicht, sobald Feld einen der Zielwerte hat (ODER)
TargetValuesProperty = nameof(Liste) Zielwerte aus einer Modell-Eigenschaft statt als Literale
DependentProperty / TargetValues Dieselben Angaben über den Objekt-Initialisierer
DisableIfNotRequired = true Feld ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Feld ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar
AllowEmptyStrings = true Von [Required] geerbt: ein leerer String gilt dann als ausgefüllt
[StringLength], [RegularExpression], [Compare] Greifen unabhängig von der Bedingung, also auch wenn das Feld gerade nicht Pflicht ist

Verhältnis zu RequiredIf

RequiredIf RequiredIfContains
Abhängige Eigenschaften eine oder mehrere genau eine
Zielwerte je Eigenschaft genau einer mehrere
Verknüpfung UND über alle Bedingungen ODER über alle Zielwerte
Zielwert-Typ object (Vergleich über ToString()) string
Zielwerte zur Laufzeit nein ja, über TargetValuesProperty

Eine Bedingung über mehrere Eigenschaften und mehrere Werte leistet keines der beiden Attribute; dafür braucht es eine eigene Validierung (IValidatableObject). Und wie RequiredIf lässt sich auch RequiredIfContains pro Eigenschaft nur einmal setzen — es ist nicht als mehrfach anwendbar deklariert.

Wie der Vergleich funktioniert

Geprüft wird, ob der Wert der abhängigen Eigenschaft (als Text) in der Liste der Zielwerte vorkommt. Der Vergleich ist exakt und gross-/kleinschreibungssensitiv"rechnung" trifft "Rechnung" nicht. Bei Auswahlfeldern ist der verglichene Wert immer der Value des Eintrags, nicht der Anzeigetext; das ist die häufigste Fehlerquelle, weil die Bedingung dann schlicht nie greift.

Ist das Ausgangsfeld eine Mehrfachauswahl (CheckboxList, MultiselectList), genügt ein Treffer: Die Bedingung trifft zu, sobald einer der gewählten Einträge in der Zielwert-Liste vorkommt (siehe Beispiel „CheckboxList als Ausgangsfeld“).

Was als «nicht ausgefüllt» gilt, entscheidet die geerbte [Required]-Logik: null, ein leerer oder nur aus Leerzeichen bestehender String — und zusätzlich eine leere Collection, was das Attribut selbst ergänzt. Auf einer Mehrfachauswahl wird daraus also eine bedingte «mindestens ein Eintrag»-Regel.

Client- und Serverseite

Serverseitig entscheidet IsRequired(...), ob RequiredAttribute.IsValid überhaupt angewandt wird. Clientseitig liefert AddValidation(...) die Attribute data-val-requiredifcontains, -dependentproperty, -targetvalues, -candisable und -canhide; der Adapter jquery.validate.unobtrusive.requiredifcontains.js wertet sie aus und markiert das Label bei aktiver Bedingung mit der Klasse required. Damit die geerbte, unbedingte [Required]-Regel nicht dazwischenfunkt, entfernt initAdditionalRequiredIfContainsLogic() sie beim Initialisieren aus dem jQuery-Validator.

Diese Attribute entstehen über Html.GetUnobtrusiveValidationAttributes(...), und das rufen nur die EditorTemplates String, Textarea, Password, DateTime, DropdownList, ComboboxList und MultiselectList auf. Trägt eine CheckboxList oder RadioList das Attribut selbst, prüft deshalb nur der Server — das betrifft immer das Feld, das [RequiredIfContains] trägt.

Das Ausgangsfeld darf dagegen jedes Template rendern: Der Adapter liest die Auswahl aus dem DOM — bei Ankreuzfeldern (Radio-Gruppe, CheckboxList) alle :checked-Werte, bei einer MultiselectList das Werte-Array, sonst den Einzelwert über val(). Bei verschachtelten ViewModels löst er den Präfix über eine Ebene auf (Container.Feld); tiefer prüft nur der Server.

Zielwerte aus einer Eigenschaft (TargetValuesProperty)

Die Werteliste steht nicht im Attribut, sondern als Daten im Modell – hier die Länder, die eine Mehrwertsteuernummer verlangen.

@model RequiredIfContainsZielwerteBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIfContains" asp-action="Zielwerte" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Land)
    @Html.EditorFor(m => m.Mehrwertsteuernummer)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class RequiredIfContainsZielwerteBeispielViewModel
    {
        public IEnumerable<SelectListItem> Laender { get; } = new List<SelectListItem>
                                                             {
                                                                 new ("Schweiz", "CH"),
                                                                 new ("Deutschland", "DE"),
                                                                 new ("Österreich", "AT"),
                                                                 new ("Frankreich", "FR"),
                                                             };

        // Die Zielwerte stehen hier als Daten und nicht als Literale im Attribut – sie könnten
        // genauso gut aus einer Konfiguration oder der Datenbank stammen.
        public string[] LaenderMitMwstNummer { get; } = { "DE", "AT", "FR" };

        [Display(Name = "Land")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Laender))]
        public string Land { get; set; }

        [Display(Name = "Mehrwertsteuernummer")]
        [UIHint("String")]
        [RequiredIfContains(
            nameof(Land),
            TargetValuesProperty = nameof(LaenderMitMwstNummer),
            ErrorMessage = "Für dieses Land ist die Mehrwertsteuernummer erforderlich.")]
        public string Mehrwertsteuernummer { get; set; }
    }
}
using System.Collections.Generic;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.Basis.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Basis.Controllers.Beispiele
{
    [Area("Basis")]
    public class RequiredIfContainsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfContainsBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfContainsBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);

                // PRG-Pattern (Post-Redirect-Get): nach erfolgreichem POST per Redirect auf die
                // GET-Action weiterleiten, damit ein erneutes Laden/Aktualisieren das Formular nicht
                // nochmals abschickt (verhindert doppeltes Speichern).
                return this.RedirectToAction(nameof(this.Index));
            }

            // Ungültige Eingabe: dieselbe Seite erneut anzeigen, damit die Validierungsfehler erscheinen.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult CheckboxList()
        {
            return this.View(new RequiredIfContainsCheckboxListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult CheckboxList(RequiredIfContainsCheckboxListBeispielViewModel modell)
        {
            // Sicherheitsnetz für den Fehlerfall: Wird die View mit den Validierungsfehlern erneut
            // gerendert, muss die Collection gesetzt sein – das CheckboxList-Template prüft nicht auf null.
            // Ohne Häkchen schickt der Browser gar keinen Wert für die Liste mit.
            modell.Verpflegung ??= new List<int>();

            if (this.ModelState.IsValid)
            {
                // Gültig: es ist weder Allergiker- noch Sonderkost angehakt oder die Angaben zur Kost
                // sind erfasst. Die Bedingung greift, sobald *ein* angehakter Wert ein Zielwert ist.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.CheckboxList));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textbox()
        {
            return this.View(new RequiredIfContainsTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfContainsTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: die Zahlungsart steht auf keinem der Zielwerte oder die Referenznummer ist erfasst.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Dropdown()
        {
            return this.View(new RequiredIfContainsDropdownBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfContainsDropdownBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Dropdown));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Zielwerte()
        {
            return this.View(new RequiredIfContainsZielwerteBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Zielwerte(RequiredIfContainsZielwerteBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Die Zielwerte liest das Attribut bei der Prüfung aus LaenderMitMwstNummer –
                // das gebundene Modell trägt die Liste also mit in die Validierung.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Zielwerte));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Ausblenden()
        {
            return this.View(new RequiredIfContainsAusblendenBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ausblenden(RequiredIfContainsAusblendenBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // HideIfNotRequired wirkt nur clientseitig: das Feld wird trotzdem mitgeschickt
                // und serverseitig geprüft.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Ausblenden));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Statt die Zielwerte als Literale ins Attribut zu schreiben, verweist TargetValuesProperty auf eine Eigenschaft des Modells. Damit werden die Werte zu Daten: Sie dürfen aus einer Konfiguration, der Datenbank oder einer Berechnung stammen und lassen sich ändern, ohne das ViewModel neu zu kompilieren.

public string[] LaenderMitMwstNummer { get; } = { "DE", "AT", "FR" };

[Display(Name = "Mehrwertsteuernummer")]
[UIHint("String")]
[RequiredIfContains(
    nameof(Land),
    TargetValuesProperty = nameof(LaenderMitMwstNummer),
    ErrorMessage = "…")]
public string Mehrwertsteuernummer { get; set; }

Optionen

Option Wirkung
[RequiredIfContains(nameof(Feld), "A", "B")] Pflicht, sobald Feld einen der Zielwerte hat (ODER)
TargetValuesProperty = nameof(Liste) Zielwerte aus einer Modell-Eigenschaft statt als Literale
DisableIfNotRequired = true Feld ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Feld ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar

Was dabei zu beachten ist

  • Die Eigenschaft muss ein Array oder eine Liste sein (string[], List<string>). Eine verzögert ausgewertete LINQ-Abfrage genügt nicht — sie ist zwar IEnumerable, aber keine IList, und die Zielwerte bleiben leer.
  • Die Eigenschaft muss im selben Objekt liegen wie die abhängige Eigenschaft; gesucht wird sie per Reflection über den Namen. nameof(...) verwenden, damit ein Umbenennen nicht stillschweigend die Bedingung aushebelt.
  • Die Werte werden zweimal gelesen: beim Rendern für die Client-Validierung (aus dem Modell der View) und bei jeder Serverprüfung erneut aus dem gebundenen Modell. Die Liste muss deshalb auch am gebundenen Modell verfügbar sein — bei einer Eigenschaft mit Initialisierer wie oben ist das automatisch der Fall, bei einer im Controller befüllten Liste muss sie im POST erneut gesetzt werden.
  • Sind sowohl Zielwerte im Konstruktor als auch TargetValuesProperty gesetzt, gewinnt die Eigenschaft — die Literale werden überschrieben.