Boolean

Das Boolean-EditorTemplate rendert einen bool? als Ja/Nein-Auswahl. Einbindung über @Html.EditorFor(...) mit [UIHint(UiEditor.Basis.Boolean)].

Boolean (Grundfall)

Ein bool? als Ja/Nein-Auswahl. Ohne Wert ist keine der beiden Optionen vorausgewählt, und ohne Pflichtangabe ist auch die leere Auswahl gültig.

@model BooleanBeispielViewModel

<form asp-area="Basis" asp-controller="Boolean" asp-action="Index" method="post">
    @Html.EditorFor(m => m.HatBestanden)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.ComponentModel.DataAnnotations;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    /// <summary>
    /// Der Grundfall: ein <c>bool?</c> ohne Pflichtauswahl. Ohne Wert ist keine der beiden
    /// Optionen vorausgewählt.
    /// </summary>
    public class BooleanBeispielViewModel
    {
        [Display(Name = "Bestanden")]
        [UIHint(UiEditor.Basis.Boolean)]
        public bool? HatBestanden { 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
{
    /// <summary>
    /// Ein Controller pro Template (Boolean); je eine Action/View pro Variante.
    /// Die POST-Actions zeigen, was die Auswahl im ModelState bewirkt: Ohne Pflichtangabe ist auch
    /// die leere Auswahl gültig, mit [Required] nicht.
    /// </summary>
    [Area("Basis")]
    public class BooleanController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new BooleanBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(BooleanBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Index));
        }

        [HttpGet]
        public IActionResult Pflicht()
        {
            return this.View(new BooleanPflichtBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Pflicht(BooleanPflichtBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Pflicht));
        }

        [HttpGet]
        public IActionResult Ressourcen()
        {
            return this.View(new BooleanRessourceBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ressourcen(BooleanRessourceBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Ressourcen));
        }

        /// <summary>
        /// Gemeinsame PRG-Logik der drei Varianten: Bei gültigem Modell eine Meldung setzen und
        /// per Redirect auf die GET-Action, sonst dieselbe View mit den Fehlern erneut anzeigen.
        /// </summary>
        /// <param name="modell">Das abgeschickte Modell.</param>
        /// <param name="action">Der Name der GET-Action dieser Variante.</param>
        /// <returns>Die Weiterleitung oder die erneut gerenderte View.</returns>
        private IActionResult PruefeUndZeige(object modell, string action)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Auswahl übernommen.", AlertType.Success, autoHide: true);

                // PRG-Pattern: nach erfolgreichem POST per Redirect auf die GET-Action, damit ein
                // erneutes Laden das Formular nicht nochmals abschickt.
                return this.RedirectToAction(action);
            }

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

Darstellung

Das Boolean-EditorTemplate rendert einen bool? als Ja/Nein-Auswahl, zwei Radio-Optionen unter einer gemeinsamen Beschriftung. Eingebunden wird es über den ASP.NET-Core-Standardmechanismus, also [UIHint(UiEditor.Basis.Boolean)] am Modell und Html.EditorFor(...) in der View, innerhalb eines Formulars.

[Display(Name = "Bestanden")]
[UIHint(UiEditor.Basis.Boolean)]
public bool? HatBestanden { get; set; }
@Html.EditorFor(m => m.HatBestanden)

bool? und nicht bool. Nur mit dem nullbaren Typ gibt es den dritten Zustand «nichts gewählt»; ein bool käme immer mit einer vorausgewählten Option an. Ohne Pflichtangabe ist die leere Auswahl gültig, das Formular lässt sich also ohne Entscheid abschicken.

Die Attribute im Überblick

Attribut Wirkung
[UIHint(UiEditor.Basis.Boolean)] Wählt die Vorlage; Standard-MVC-Mechanik, die Konstante kommt aus UiEditor.Basis (FMH.Komponente.Basis.Validierung)
[Display(Name = "…")] Beschriftung über den beiden Optionen
[Required] Pflichtauswahl, siehe Beispiel «Boolean mit Pflichtauswahl»; das Label bekommt zusätzlich die Klasse required-outline
[AdditionalMetadata(UiMetadata.Basis.LabelTrue, "…")] Beschriftung der Ja-Option; ohne Angabe der Text aus _FmhWebMvc.Yes
[AdditionalMetadata(UiMetadata.Basis.LabelFalse, "…")] Beschriftung der Nein-Option; ohne Angabe der Text aus _FmhWebMvc.No
[HelpText(Name = "…")] Ergänzender Hilfetext unter den Optionen; er weicht der Fehlermeldung, sobald eine erscheint
[RequiredIf(...)] Pflicht nur unter einer Bedingung, siehe Basis.Validierung › Validierung › RequiredIf
[UniqueId] Hängt ein Zufalls-Suffix an die Id, wenn dasselbe Formular mehrfach auf der Seite steht

Die beiden Label-Metadaten brauchen keinen Provider. Das Template liest sie über AdditionalMetadataValue(...) direkt am Modell-Member, nicht aus ModelMetadata.AdditionalValues. Anders als beim Icon der Auswahlbox oder beim Fettdruck der Textbox wirkt hier also auch dann alles, wenn AddFmhWidgetOptions() fehlt.

Boolean mit Labels aus Ressourcen

Die Beschriftung der Nein-Option kommt als Ressourcen-Schlüssel über ResourceType und folgt damit der Sprache der Anfrage.

@model BooleanRessourceBeispielViewModel

<form asp-area="Basis" asp-controller="Boolean" asp-action="Ressourcen" method="post">
    @Html.EditorFor(m => m.IstFertig)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using FMH.Komponente.Infrastruktur;
using FMH.Komponente.Infrastruktur.Resources;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    /// <summary>
    /// Beschriftungen aus einer Ressource: Der Wert ist dann der Ressourcen-Schlüssel, aufgelöst
    /// wird er über <c>ResourceType</c> in der Sprache der Anfrage.
    /// </summary>
    /// <remarks>
    /// Absichtlich gemischt: Die Ja-Seite trägt einen festen Text, die Nein-Seite kommt aus der
    /// Ressource der Infrastruktur. Beides nebeneinander ist zulässig.
    /// </remarks>
    public class BooleanRessourceBeispielViewModel
    {
        [Display(Name = "Bist du mit der Aufgabe fertig?")]
        [UIHint(UiEditor.Basis.Boolean)]
        [AdditionalMetadata(UiMetadata.Basis.LabelTrue, "Ja, alles erledigt")]
        [AdditionalMetadata(UiMetadata.Basis.LabelFalse, nameof(_FmhWebMvc.No), ResourceType = typeof(_FmhWebMvc))]
        public bool? IstFertig { 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
{
    /// <summary>
    /// Ein Controller pro Template (Boolean); je eine Action/View pro Variante.
    /// Die POST-Actions zeigen, was die Auswahl im ModelState bewirkt: Ohne Pflichtangabe ist auch
    /// die leere Auswahl gültig, mit [Required] nicht.
    /// </summary>
    [Area("Basis")]
    public class BooleanController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new BooleanBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(BooleanBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Index));
        }

        [HttpGet]
        public IActionResult Pflicht()
        {
            return this.View(new BooleanPflichtBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Pflicht(BooleanPflichtBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Pflicht));
        }

        [HttpGet]
        public IActionResult Ressourcen()
        {
            return this.View(new BooleanRessourceBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ressourcen(BooleanRessourceBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Ressourcen));
        }

        /// <summary>
        /// Gemeinsame PRG-Logik der drei Varianten: Bei gültigem Modell eine Meldung setzen und
        /// per Redirect auf die GET-Action, sonst dieselbe View mit den Fehlern erneut anzeigen.
        /// </summary>
        /// <param name="modell">Das abgeschickte Modell.</param>
        /// <param name="action">Der Name der GET-Action dieser Variante.</param>
        /// <returns>Die Weiterleitung oder die erneut gerenderte View.</returns>
        private IActionResult PruefeUndZeige(object modell, string action)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Auswahl übernommen.", AlertType.Success, autoHide: true);

                // PRG-Pattern: nach erfolgreichem POST per Redirect auf die GET-Action, damit ein
                // erneutes Laden das Formular nicht nochmals abschickt.
                return this.RedirectToAction(action);
            }

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

Darstellung

Statt eines festen Textes nimmt AdditionalMetadata einen Ressourcen-Schlüssel, sobald ResourceType gesetzt ist. Aufgelöst wird er zur Renderzeit in der Sprache der Anfrage:

[Display(Name = "Bist du mit der Aufgabe fertig?")]
[UIHint(UiEditor.Basis.Boolean)]
[AdditionalMetadata(UiMetadata.Basis.LabelTrue, "Ja, alles erledigt")]
[AdditionalMetadata(UiMetadata.Basis.LabelFalse, nameof(_FmhWebMvc.No), ResourceType = typeof(_FmhWebMvc))]
public bool? IstFertig { get; set; }

Beides nebeneinander ist zulässig, wie hier: fester Text auf der Ja-Seite, Ressource auf der Nein-Seite. Aufgelöst wird über einen DisplayAttribute mit demselben ResourceType, es gelten also dieselben Regeln wie bei [Display(Name = …, ResourceType = …)]: Der Schlüssel muss als öffentliche Eigenschaft in der Ressourcenklasse liegen, sonst bleibt die Beschriftung leer.

_FmhWebMvc ist die Ressource der Infrastruktur, aus der auch die Vorgaben «Ja» und «Nein» stammen; eine Anwendung setzt hier ebenso gut ihre eigene Ressourcenklasse ein.

Boolean mit Pflichtauswahl

[Required] macht die Auswahl zur Pflicht, dazu eigene Beschriftungen für Ja und Nein. Absenden ohne Auswahl zeigt die Meldung.

@model BooleanPflichtBeispielViewModel

<form asp-area="Basis" asp-controller="Boolean" asp-action="Pflicht" method="post">
    @Html.EditorFor(m => m.IstErledigt)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-2" }))
</form>
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using FMH.Komponente.Infrastruktur;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    /// <summary>
    /// Pflichtauswahl mit eigenen Beschriftungen der beiden Optionen.
    /// </summary>
    public class BooleanPflichtBeispielViewModel
    {
        [Display(Name = "Erledigt")]
        [UIHint(UiEditor.Basis.Boolean)]
        [AdditionalMetadata(UiMetadata.Basis.LabelTrue, "Ja, ist erledigt")]
        [AdditionalMetadata(UiMetadata.Basis.LabelFalse, "Nein, noch offen")]
        [Required(ErrorMessage = "Bitte angeben, ob die Aufgabe erledigt ist.")]
        public bool? IstErledigt { 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
{
    /// <summary>
    /// Ein Controller pro Template (Boolean); je eine Action/View pro Variante.
    /// Die POST-Actions zeigen, was die Auswahl im ModelState bewirkt: Ohne Pflichtangabe ist auch
    /// die leere Auswahl gültig, mit [Required] nicht.
    /// </summary>
    [Area("Basis")]
    public class BooleanController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new BooleanBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(BooleanBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Index));
        }

        [HttpGet]
        public IActionResult Pflicht()
        {
            return this.View(new BooleanPflichtBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Pflicht(BooleanPflichtBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Pflicht));
        }

        [HttpGet]
        public IActionResult Ressourcen()
        {
            return this.View(new BooleanRessourceBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Ressourcen(BooleanRessourceBeispielViewModel modell)
        {
            return this.PruefeUndZeige(modell, nameof(this.Ressourcen));
        }

        /// <summary>
        /// Gemeinsame PRG-Logik der drei Varianten: Bei gültigem Modell eine Meldung setzen und
        /// per Redirect auf die GET-Action, sonst dieselbe View mit den Fehlern erneut anzeigen.
        /// </summary>
        /// <param name="modell">Das abgeschickte Modell.</param>
        /// <param name="action">Der Name der GET-Action dieser Variante.</param>
        /// <returns>Die Weiterleitung oder die erneut gerenderte View.</returns>
        private IActionResult PruefeUndZeige(object modell, string action)
        {
            if (this.ModelState.IsValid)
            {
                // Erfolgsfall: hier würden die eigentlichen Aktionen ausgeführt (z. B. Speichern).
                this.AddAlert("Formular ist gültig – Auswahl übernommen.", AlertType.Success, autoHide: true);

                // PRG-Pattern: nach erfolgreichem POST per Redirect auf die GET-Action, damit ein
                // erneutes Laden das Formular nicht nochmals abschickt.
                return this.RedirectToAction(action);
            }

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

Darstellung

[Required] macht die Auswahl zur Pflicht: Solange keine Option gewählt ist, ist der bool? null und das Formular ungültig.

[Display(Name = "Erledigt")]
[UIHint(UiEditor.Basis.Boolean)]
[AdditionalMetadata(UiMetadata.Basis.LabelTrue, "Ja, ist erledigt")]
[AdditionalMetadata(UiMetadata.Basis.LabelFalse, "Nein, noch offen")]
[Required(ErrorMessage = "Bitte angeben, ob die Aufgabe erledigt ist.")]
public bool? IstErledigt { get; set; }

Was dabei zu sehen ist:

  • Die Beschriftung wird ausgezeichnet. Bei Pflichtangabe hängt das Template die Klasse required-outline an das Label, die Kennzeichnung kommt also ohne Zutun der Seite.
  • Geprüft wird server- und clientseitig. [Required] ist Standard-MVC: Die unaufdringliche Validierung meldet schon beim Absenden im Browser, der POST prüft erneut. Der Tab «Controller (GET/POST)» zeigt das PRG-Muster dahinter: gültig führt zu einem Redirect, ungültig rendert dieselbe Seite mit der Meldung.
  • Die Meldung steht unter den Optionen, im Element msg_<id>; ein [HelpText] an derselben Eigenschaft tritt zurück, solange ein Fehler angezeigt wird.

Für eine Pflicht, die nur unter einer Bedingung gilt, gibt es [RequiredIf], beschrieben unter Basis.Validierung › Validierung › RequiredIf.