RequiredIf

Das RequiredIfAttribute macht ein Feld nur dann zum Pflichtfeld, wenn eine andere Eigenschaft einen bestimmten Wert hat. Jedes Beispiel steht für sich: ein eigener Anwendungsfall mit eigener Bedingung auf einem anderen EditorTemplate, gewählt über [UIHint(...)]. Jedes Beispiel zeigt in Tabs die cshtml, das ViewModel und den Controller (GET/POST); darunter das funktionierende Formular.

ComboboxList (UIHint "ComboboxList")

Auf dem durchsuchbaren Auswahlfeld: das Fachgebiet wird bei der Terminart „Facharzttermin“ zur Pflicht.

@model RequiredIfComboboxBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Combobox" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Terminart)
    @Html.EditorFor(m => m.Fachgebiet)
    @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 RequiredIfComboboxBeispielViewModel
    {
        public IEnumerable<SelectListItem> Terminarten { get; } = new List<SelectListItem>
                                                                  {
                                                                      new ("Sprechstunde", "Sprechstunde"),
                                                                      new ("Facharzttermin", "Facharzt"),
                                                                  };

        public IEnumerable<SelectListItem> Fachgebiete { get; } = new List<SelectListItem>
                                                                  {
                                                                      new ("Kardiologie", "KAR"),
                                                                      new ("Dermatologie", "DER"),
                                                                      new ("Neurologie", "NEU"),
                                                                      new ("Onkologie", "ONK"),
                                                                      new ("Radiologie", "RAD"),
                                                                  };

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

        [Display(Name = "Fachgebiet (durchsuchbar)")]
        [UIHint("ComboboxList")]
        [AdditionalMetadata("Selection", nameof(Fachgebiete))]
        [RequiredIf(nameof(Terminart), "Facharzt", ErrorMessage = "Bitte das Fachgebiet wählen, wenn die Terminart „Facharzttermin“ gewählt ist.")]
        public string Fachgebiet { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Dasselbe auf dem durchsuchbaren Auswahlfeld — sinnvoll bei längeren Listen wie einem Fachgebiet. Gültig ist der Value des gewählten Eintrags; solange nur der leere Platzhalter dasteht, gilt das Feld als nicht ausgefüllt.

[Display(Name = "Fachgebiet (durchsuchbar)")]
[UIHint("ComboboxList")]
[AdditionalMetadata("Selection", nameof(Fachgebiete))]
[RequiredIf(nameof(Terminart), "Facharzt", ErrorMessage = "…")]
public string Fachgebiet { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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

Besonderheiten dieses Templates

  • Wird die Bedingung durch eine Eingabe wahr, springt der Fokus auf das Feld und die Auswahlliste klappt automatisch auf — die Combobox ist das einzige Template mit diesem Verhalten.
  • [EmptyEntry(false)] unterdrückt den leeren Platzhalter. Dann ist immer ein Wert gewählt und die Bedingung läuft ins Leere — die Kombination ergibt nur mit einer Vorauswahl Sinn, die fachlich als „noch nichts gewählt“ gilt.
  • Sollen mehrere Werte des Ausgangsfelds die Pflicht auslösen, ist [RequiredIfContains(nameof(Terminart), "Facharzt", "Notfall")] das passendere Attribut.

Datumsfeld (UIHint "DateTime")

Auf einem DateTime?-Feld: das Vertragsende wird bei einer befristeten Anstellung zur Pflicht.

@model RequiredIfDatumBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Datum" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Anstellung)
    @Html.EditorFor(m => m.Vertragsende)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System;
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 RequiredIfDatumBeispielViewModel
    {
        public IEnumerable<SelectListItem> Anstellungsformen { get; } = new List<SelectListItem>
                                                                        {
                                                                            new ("Unbefristet", "Unbefristet"),
                                                                            new ("Befristet", "Befristet"),
                                                                        };

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

        [Display(Name = "Vertragsende")]
        [UIHint("DateTime")]
        [RequiredIf(nameof(Anstellung), "Befristet", ErrorMessage = "Bitte das Vertragsende angeben, wenn die Anstellung „Befristet“ ist.")]
        public DateTime? Vertragsende { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Auf einem DateTime?-Feld ist der Wert ohne Eingabe null und damit «nicht ausgefüllt». Ein Datum braucht keine Sonderbehandlung — die Prüfung ist dieselbe wie bei einem Textfeld, nur dass es keinen „leeren Text“ gibt, sondern nur null.

[Display(Name = "Vertragsende")]
[UIHint("DateTime")]
[RequiredIf(nameof(Anstellung), "Befristet", ErrorMessage = "…")]
public DateTime? Vertragsende { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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

Besonderheiten dieses Templates

  • Der Typ muss nullable sein. Ein DateTime ohne ? hat als Standardwert 01.01.0001 und ist damit nie leer — die Bedingung könnte nie greifen.
  • Bereichsgrenzen sind davon unabhängig: dafür zusätzlich [DatumBereich(...)] setzen. Die beiden Attribute stören sich nicht, RequiredIf entscheidet über „muss ausgefüllt sein“, DatumBereich über „welche Werte sind erlaubt“.
  • Mit DisableIfNotRequired = true wird das Feld nicht nur gesperrt, sondern auch aus der Tab-Reihenfolge genommen (tabindex="-1") — der Datepicker lässt sich sonst per Tastatur trotz gesperrtem Feld öffnen.

DropdownList (UIHint "DropdownList")

Auf einem Auswahlfeld: das Zustellfenster wird bei der Versandart „Express“ zur Pflicht – der leere Platzhalter-Eintrag zählt als «nicht ausgefüllt».

@model RequiredIfDropdownBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Dropdown" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Versandart)
    @Html.EditorFor(m => m.Zustellfenster)
    @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 RequiredIfDropdownBeispielViewModel
    {
        public IEnumerable<SelectListItem> Versandarten { get; } = new List<SelectListItem>
                                                                   {
                                                                       new ("Standard", "Standard"),
                                                                       new ("Express", "Express"),
                                                                   };

        public IEnumerable<SelectListItem> Zeitfenster { get; } = new List<SelectListItem>
                                                                  {
                                                                      new ("Vormittag", "VM"),
                                                                      new ("Nachmittag", "NM"),
                                                                      new ("Abend", "AB"),
                                                                  };

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

        [Display(Name = "Zustellfenster")]
        [UIHint("DropdownList")]
        [AdditionalMetadata("Selection", nameof(Zeitfenster))]
        [RequiredIf(nameof(Versandart), "Express", ErrorMessage = "Bitte das Zustellfenster wählen, wenn die Versandart „Express“ gewählt ist.")]
        public string Zustellfenster { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            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 die Bedingung nicht zutrifft.

[Display(Name = "Zustellfenster")]
[UIHint("DropdownList")]
[AdditionalMetadata("Selection", nameof(Zeitfenster))]
[RequiredIf(nameof(Versandart), "Express", ErrorMessage = "…")]
public string Zustellfenster { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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 läuft ins Leere.
  • Ein Auswahlfeld eignet sich sowohl als geschütztes Feld (wie hier) als auch als Ausgangsfeld einer Bedingung — der Zielwert ist dann der Value des Eintrags, nicht sein Anzeigetext.
  • Soll die Pflicht bei mehreren Versandarten greifen, ist [RequiredIfContains(nameof(Versandart), "Express", "Kurier")] der direkte Weg. Ein zweites [RequiredIf] am selben Feld ist keine Alternative — das Attribut erlaubt kein Mehrfachvorkommen und lässt sich nur einmal je Eigenschaft setzen.

MultiselectList (UIHint "MultiselectList")

Auf einer Mehrfachauswahl: mindestens ein Zusatzmodul wird bei der Mitgliedschaft „Plus“ zur Pflicht – eine leere Auswahl gilt als nicht ausgefüllt.

@model RequiredIfMultiselectBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Multiselect" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Mitgliedschaft)
    @Html.EditorFor(m => m.Zusatzmodule)
    @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 RequiredIfMultiselectBeispielViewModel
    {
        public IEnumerable<SelectListItem> Mitgliedschaftsarten { get; } = new List<SelectListItem>
                                                                          {
                                                                              new ("Basis", "Basis"),
                                                                              new ("Plus", "Plus"),
                                                                          };

        public IEnumerable<SelectListItem> Module { get; } = new List<SelectListItem>
                                                             {
                                                                 new ("Fortbildung", "FOR"),
                                                                 new ("Rechtsberatung", "REC"),
                                                                 new ("Praxissoftware", "PRA"),
                                                             };

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

        // Bei einer Collection gilt eine leere Auswahl als «nicht ausgefüllt».
        [Display(Name = "Zusatzmodule")]
        [UIHint("MultiselectList")]
        [AdditionalMetadata("Selection", nameof(Module))]
        [RequiredIf(nameof(Mitgliedschaft), "Plus", ErrorMessage = "Bitte mindestens ein Zusatzmodul wählen, wenn die Mitgliedschaft „Plus“ gewählt ist.")]
        public string[] Zusatzmodule { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Ist der geprüfte Wert eine Collection (ICollection), gilt eine leere Collection als nicht ausgefüllt — IsValid reicht in diesem Fall null an die Basisprüfung weiter. Aus dem Attribut wird damit eine bedingte «mindestens ein Eintrag»-Regel.

[Display(Name = "Zusatzmodule")]
[UIHint("MultiselectList")]
[AdditionalMetadata("Selection", nameof(Module))]
[RequiredIf(nameof(Mitgliedschaft), "Plus", ErrorMessage = "…")]
public string[] Zusatzmodule { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Mindestens ein Eintrag Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
DisableIfNotRequired = true Auswahl ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Auswahl ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar
[MinLength(n)] Verlangt immer n Einträge, unabhängig von der Bedingung — bewusst kombinieren

Besonderheiten dieses Templates

  • Das Attribut prüft nur „mindestens ein Eintrag“. Eine bedingte Mindestanzahl grösser eins gibt es nicht; dafür braucht es eine eigene Validierung (IValidatableObject).
  • Als Ausgangsfeld ist eine Mehrfachauswahl dagegen nicht brauchbar: Serverseitig wird der Wert der abhängigen Eigenschaft über ToString() verglichen, was bei einem string[] den Typnamen ergibt und deshalb nie auf einen Zielwert passt. Clientseitig läse der Adapter nur den ersten angekreuzten Wert. Eine Bedingung braucht ein Feld mit genau einem Wert.

Passwortfeld (UIHint "Password")

Auf einem Passwortfeld: das Dokumentkennwort wird beim Zugriffsschutz „Verschlüsselt“ zur Pflicht.

@model RequiredIfPasswordBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Password" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Zugriffsschutz)
    @Html.EditorFor(m => m.Dokumentkennwort)
    @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 RequiredIfPasswordBeispielViewModel
    {
        public IEnumerable<SelectListItem> Schutzstufen { get; } = new List<SelectListItem>
                                                                   {
                                                                       new ("Offen", "Offen"),
                                                                       new ("Verschlüsselt", "Verschluesselt"),
                                                                   };

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

        [Display(Name = "Dokumentkennwort")]
        [UIHint("Password")]
        [RequiredIf(nameof(Zugriffsschutz), "Verschluesselt", ErrorMessage = "Bitte das Dokumentkennwort setzen, wenn der Zugriffsschutz „Verschlüsselt“ gewählt ist.")]
        public string Dokumentkennwort { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

visibility

Auf dem Passwortfeld gilt dasselbe; der Wert wird wie bei jedem Textfeld auf «leer» geprüft. Ein verschlüsselter Versand verlangt hier ein Kennwort.

[Display(Name = "Dokumentkennwort")]
[UIHint("Password")]
[RequiredIf(nameof(Zugriffsschutz), "Verschluesselt", ErrorMessage = "…")]
public string Dokumentkennwort { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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
[StringLength(n, MinimumLength = m)] Längenregeln – greifen unabhängig von der Bedingung

Besonderheiten dieses Templates

  • Der Zielwert ist der Wert des Auswahleintrags ("Verschluesselt"), nicht dessen Anzeigetext ("Verschlüsselt"). Das ist die häufigste Fehlerquelle: die Bedingung greift dann schlicht nie.
  • DisableIfNotRequired = true ist bei einem Kennwortfeld mit Vorsicht zu verwenden: ein gesperrtes Feld wird vom Browser nicht mitgeschickt, ein bereits eingegebener Wert geht beim Umschalten der Bedingung also verloren. HideIfNotRequired blendet nur aus und schickt weiterhin mit.

RadioList als Ausgangsfeld

Umgekehrte Blickrichtung: Nicht das geschützte Feld, sondern die Bedingung liegt auf einer Radio-Gruppe – die E-Mail-Adresse wird bei der Teilnahmeform „Online“ zur Pflicht.

@model RequiredIfRadioListBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="RadioList" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Teilnahmeform)
    @Html.EditorFor(m => m.Zugangsmail)
    @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 RequiredIfRadioListBeispielViewModel
    {
        public IEnumerable<SelectListItem> Teilnahmeformen { get; } = new List<SelectListItem>
                                                                      {
                                                                          new ("Vor Ort", "VorOrt"),
                                                                          new ("Online", "Online"),
                                                                      };

        [Display(Name = "Teilnahmeform")]
        [UIHint("RadioList")]
        [AdditionalMetadata("Selection", nameof(Teilnahmeformen))]
        [AdditionalMetadata("Direction", FlexDirection.Row)]
        public string Teilnahmeform { get; set; } = "VorOrt";

        [Display(Name = "E-Mail für den Zugangslink")]
        [UIHint("String")]
        [RequiredIf(nameof(Teilnahmeform), "Online", ErrorMessage = "Bitte die E-Mail-Adresse angeben, wenn die Teilnahme „Online“ erfolgt.")]
        public string Zugangsmail { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Die übrigen Beispiele variieren das geschützte Feld; hier variiert das Ausgangsfeld. DependentProperties verweist auf eine ganz normale Eigenschaft — welches EditorTemplate sie rendert, ist für die Bedingung gleichgültig. Eine Radio-Gruppe eignet sich als Ausgangsfeld besonders, wenn es nur zwei, drei Möglichkeiten gibt und diese ohne Aufklappen sichtbar bleiben sollen.

[Display(Name = "Teilnahmeform")]
[UIHint("RadioList")]
[AdditionalMetadata("Selection", nameof(Teilnahmeformen))]
[AdditionalMetadata("Direction", FlexDirection.Row)]
public string Teilnahmeform { get; set; } = "VorOrt";

[Display(Name = "E-Mail für den Zugangslink")]
[UIHint("String")]
[RequiredIf(nameof(Teilnahmeform), "Online", ErrorMessage = "…")]
public string Zugangsmail { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
DisableIfNotRequired = true Geschütztes Feld ist gesperrt, solange die Bedingung nicht zutrifft
HideIfNotRequired = true Geschütztes Feld ist ausgeblendet, solange die Bedingung nicht zutrifft
ErrorMessage / ErrorMessageResourceType + -Name Meldung wie bei [Required], lokalisierbar

Besonderheiten als Ausgangsfeld

  • Der Zielwert ist auch hier der Wert des Eintrags ("Online"), nicht dessen Anzeigetext.
  • Clientseitig liest der Adapter den angekreuzten Radio-Button über :checked und hängt sich an dessen change-Ereignis — die Pflichtmarkierung wechselt also schon beim Umschalten, nicht erst beim Absenden.
  • Anders als bei einer DropdownList gibt es keinen leeren Platzhalter-Eintrag: Ist keine Vorauswahl gesetzt, ist zunächst kein Radio-Button gewählt und die Bedingung trifft nicht zu.
  • Die Einschränkung, dass RadioList selbst keine Client-Validierungsattribute rendert (siehe Abschnitt „Textbox“), betrifft nur das Feld, das [RequiredIf] trägt — als Ausgangsfeld ist die Radio-Gruppe uneingeschränkt brauchbar.

Textarea (UIHint "Textarea")

Auf einem mehrzeiligen Feld: die Beanstandung wird bei der Bewertung „Unzufrieden“ zur Pflicht.

@model RequiredIfTextareaBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Textarea" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Bewertung)
    @Html.EditorFor(m => m.Beanstandung)
    @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 RequiredIfTextareaBeispielViewModel
    {
        public IEnumerable<SelectListItem> Bewertungen { get; } = new List<SelectListItem>
                                                                  {
                                                                      new ("Zufrieden", "Zufrieden"),
                                                                      new ("Unzufrieden", "Unzufrieden"),
                                                                  };

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

        [Display(Name = "Beanstandung")]
        [UIHint("Textarea")]
        [RequiredIf(nameof(Bewertung), "Unzufrieden", ErrorMessage = "Bitte die Beanstandung beschreiben, wenn die Bewertung „Unzufrieden“ lautet.")]
        public string Beanstandung { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

0/1000

Auf einem mehrzeiligen Feld gilt dasselbe Muster — es ändert sich nur der UIHint. Hier verlangt eine negative Bewertung eine Begründung.

[Display(Name = "Beanstandung")]
[UIHint("Textarea")]
[RequiredIf(nameof(Bewertung), "Unzufrieden", ErrorMessage = "…")]
public string Beanstandung { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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(n)] Maximallänge; das Template zeigt dazu einen Zeichenzähler

Besonderheiten dieses Templates

  • Nur Leerzeichen und Zeilenumbrüche zählen als leer. Server- wie clientseitig wird der Wert vor der Prüfung getrimmt — eine Begründung aus drei Leerzeichen besteht die Validierung also nicht.
  • Wer bewusst auch eine leere Eingabe akzeptieren will (etwa weil ein separates Kennzeichen die Antwort trägt), setzt AllowEmptyStrings = true. Dann prüft das Attribut nur noch auf null.

Textbox (UIHint "String")

Die Telefonnummer wird zur Pflicht, sobald als Kontaktweg „Telefon“ gewählt ist.

@model RequiredIfTextboxBeispielViewModel

<form asp-area="Basis" asp-controller="RequiredIf" asp-action="Textbox" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Kontaktweg)
    @Html.EditorFor(m => m.Telefonnummer)
    @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 RequiredIfTextboxBeispielViewModel
    {
        public IEnumerable<SelectListItem> Kontaktwege { get; } = new List<SelectListItem>
                                                                  {
                                                                      new ("E-Mail", "Mail"),
                                                                      new ("Telefon", "Telefon"),
                                                                  };

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

        [Display(Name = "Telefonnummer")]
        [UIHint("String")]
        [RequiredIf(nameof(Kontaktweg), "Telefon", ErrorMessage = "Bitte die Telefonnummer angeben, wenn als Kontaktweg „Telefon“ gewählt ist.")]
        public string Telefonnummer { get; set; }
    }
}
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
{
    // Ein Controller pro Attribut (RequiredIf); je eine Action/View pro EditorTemplate.
    // Die POST-Actions illustrieren die Validierungslogik: trifft die Bedingung zu und ist das
    // Feld leer, ist ModelState.IsValid false.
    [Area("Basis")]
    public class RequiredIfController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new RequiredIfBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(RequiredIfBeispielViewModel 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 Textbox()
        {
            return this.View(new RequiredIfTextboxBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Textbox(RequiredIfTextboxBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: entweder trifft die Bedingung nicht zu oder das Feld ist ausgefüllt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Textbox));
            }

            // Ungültig: Bedingung trifft zu und das Feld ist leer -> View mit den Fehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Textarea()
        {
            return this.View(new RequiredIfTextareaBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Password()
        {
            return this.View(new RequiredIfPasswordBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Datum()
        {
            return this.View(new RequiredIfDatumBeispielViewModel());
        }

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

            return this.View(modell);
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Dropdown(RequiredIfDropdownBeispielViewModel 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 Combobox()
        {
            return this.View(new RequiredIfComboboxBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Multiselect()
        {
            return this.View(new RequiredIfMultiselectBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Multiselect(RequiredIfMultiselectBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Bei einer Collection prüft das Attribut zusätzlich auf Count > 0.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Multiselect));
            }

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult RadioList()
        {
            return this.View(new RequiredIfRadioListBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult RadioList(RequiredIfRadioListBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Hier liefert die Bedingung eine Radio-Gruppe: gültig, solange „Vor Ort“ gewählt ist
                // oder bei „Online“ eine E-Mail-Adresse erfasst wurde.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.RadioList));
            }

            return this.View(modell);
        }
    }
}

Darstellung

Das RequiredIfAttribute erbt von RequiredAttribute und macht ein Feld nur dann zum Pflichtfeld, wenn eine andere Eigenschaft einen bestimmten Wert hat. Trifft die Bedingung nicht zu, darf das Feld leer bleiben. Dieser Abschnitt beschreibt das Attribut vollständig; die übrigen Beispiele zeigen, was sich je EditorTemplate daran ändert.

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

[Display(Name = "Telefonnummer")]
[UIHint("String")]
[RequiredIf(nameof(Kontaktweg), "Telefon", ErrorMessage = "Bitte die Telefonnummer angeben, …")]
public string Telefonnummer { get; set; }

Optionen

Option Wirkung
[RequiredIf(nameof(Feld), "Wert")] Pflicht, sobald Feld den Wert "Wert" hat
[RequiredIf(nameof(A), "1", nameof(B), "2")] Zwei Bedingungen – beide müssen zutreffen (UND)
DependentProperties / TargetValues Mehr als zwei Bedingungen, über den Objekt-Initialisierer gesetzt
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], [EmailAddress] Greifen unabhängig von der Bedingung, also auch wenn das Feld gerade nicht Pflicht ist

Mehrere Bedingungen

Zwei Bedingungen nimmt der Konstruktor direkt entgegen, mehr gehen über den Objekt-Initialisierer. In beiden Fällen gilt UND: Das Feld wird erst zur Pflicht, wenn alle Bedingungen zutreffen.

[RequiredIf(nameof(Anstellung), "Befristet", nameof(Bereich), "Extern")]
public string Vertragsnummer { get; set; }

[RequiredIf(
    DependentProperties = new[] { nameof(Anstellung), nameof(Bereich), nameof(Standort) },
    TargetValues = new object[] { "Befristet", "Extern", "Zuerich" })]
public string Zutrittsausweis { get; set; }

Ein ODER über mehrere Werte derselben Eigenschaft leistet RequiredIf nicht — dafür gibt es [RequiredIfContains(nameof(Zahlungsart), "Rechnung", "Kreditkarte")] (Beispiel auf der Seite „String“). Ein zweites [RequiredIf] am selben Feld ist kein Ausweg: Das Attribut ist nicht als mehrfach anwendbar deklariert und lässt sich pro Eigenschaft nur einmal setzen.

Wie der Vergleich funktioniert

Der Vergleich läuft über ToString() beider Werte; der Zielwert (object) muss also nicht typgleich zur abhängigen Eigenschaft sein — true, "True" und 1 bei einem bool? verhalten sich entsprechend unterschiedlich. nameof(...) statt eines Literals verwenden, damit ein Umbenennen der Eigenschaft die Bedingung nicht stillschweigend bricht.

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 RequiredIf selbst ergänzt (siehe Beispiel „MultiselectList“).

DisableIfNotRequired und HideIfNotRequired

Beide wirken rein clientseitig und ändern nichts an der Serverprüfung.

  • HideIfNotRequired = true blendet die Feldgruppe aus. Das Feld bleibt im Formular und wird mitgeschickt.
  • DisableIfNotRequired = true sperrt das Eingabefeld. Ein gesperrtes Feld schickt der Browser nicht mit — ein zuvor eingegebener Wert geht beim Umschalten der Bedingung verloren. Wo das stört, ist HideIfNotRequired die bessere Wahl.
  • Wird die Bedingung durch eine Eingabe wahr, setzt die Logik zusätzlich den Fokus ins Feld — bei einer Combobox öffnet sich die Auswahlliste gleich mit.

Client- und Serverseite

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

Die Bedingung darf sich auf eine Eigenschaft im selben Objekt beziehen; bei verschachtelten ViewModels löst der Adapter den Präfix über eine Ebene auf (Container.Feld). Tiefere Verschachtelungen findet er nicht mehr — dort prüft nur der Server.

Diese Attribute entstehen über Html.GetUnobtrusiveValidationAttributes(...), und das rufen nur die EditorTemplates String, Textarea, Password, DateTime, DropdownList, ComboboxList und MultiselectList auf — genau die Kombinationen, die diese Seite zeigt. CheckboxList rendert stattdessen bei erkanntem Pflichtfeld ein unbedingtes data-val-required (die Bedingung würde clientseitig ignoriert), RadioList rendert gar keine Validierungsattribute; in beiden Fällen prüft nur der Server.

Diese Einschränkung betrifft ausschliesslich das Feld, das [RequiredIf] trägt. Als Ausgangsfeld ist jedes Template geeignet – auch RadioList und CheckboxList –, denn dort liest der Adapter den Wert einfach aus dem DOM (:checked bei mehreren gleichnamigen Feldern, sonst val()).