Checkbox

Das Checkbox-EditorTemplate rendert einen bool? als einzelne Checkbox. Einbindung über @Html.EditorFor(...) mit [UIHint(UiEditor.Basis.Checkbox)]. Für eine Pflicht-Checkbox siehe Validierung → CheckboxRequired.

Checkbox (Grundfall)

Ein Kennzeichen als einzelne Checkbox mit Beschriftung daneben. Ohne Pflichtangabe ist auch der fehlende Haken gültig.

@model CheckboxBeispielViewModel

<form asp-area="Basis" asp-controller="Checkbox" asp-action="Index" method="post">
    @Html.EditorFor(m => m.Newsletter)
    @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 einfaches Ja/Nein-Kennzeichen als einzelne Checkbox.
    /// </summary>
    public class CheckboxBeispielViewModel
    {
        [Display(Name = "Ich möchte Werbemails erhalten")]
        [UIHint(UiEditor.Basis.Checkbox)]
        public bool Newsletter { 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 (Checkbox); je eine Action/View pro Variante.
    /// Die POST-Actions zeigen den Unterschied: Ohne Pflichtangabe ist auch die leere Checkbox
    /// gültig, mit [CheckboxRequired] nicht.
    /// </summary>
    [Area("Basis")]
    public class CheckboxController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new CheckboxBeispielViewModel());
        }

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

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

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

        /// <summary>
        /// Gemeinsame PRG-Logik der 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 – Eingabe übernommen.", AlertType.Success, autoHide: true);

                // PRG-Pattern: nach erfolgreichem POST per Redirect auf die GET-Action.
                return this.RedirectToAction(action);
            }

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

Darstellung

Das Checkbox-EditorTemplate rendert ein einzelnes Kennzeichen als Checkbox mit Beschriftung daneben. Eingebunden wird es über den ASP.NET-Core-Standardmechanismus, also [UIHint(UiEditor.Basis.Checkbox)] am Modell und Html.EditorFor(...) in der View, innerhalb eines Formulars.

[Display(Name = "Ich möchte Werbemails erhalten")]
[UIHint(UiEditor.Basis.Checkbox)]
public bool Newsletter { get; set; }
@Html.EditorFor(m => m.Newsletter)

bool und bool? sind beide zulässig. Das Template arbeitet intern mit bool? und rendert Model ?? false; ein nicht angekreuztes Feld kommt als false beziehungsweise null zurück. Für eine Auswahl mit drei Zuständen ist nicht dieses Template gedacht, sondern Boolean mit seinen zwei Optionen.

Mehrere Checkboxen zu einer Auswahl gehören nicht hierher, dafür gibt es CheckboxList mit IEnumerable<int> und der Auswahlliste am Modell.

Die Attribute im Überblick

Attribut Wirkung
[UIHint(UiEditor.Basis.Checkbox)] Wählt die Vorlage; Standard-MVC-Mechanik, die Konstante kommt aus UiEditor.Basis (FMH.Komponente.Basis.Validierung)
[Display(Name = "…")] Beschriftung neben der Checkbox
[CheckboxRequired(ErrorMessage = "…")] Verlangt den Haken, siehe Beispiel «Checkbox mit Pflicht-Ankreuzung»
[HelpText(Name = "…")] Ergänzender Hilfetext unter der Checkbox; er weicht der Fehlermeldung, sobald eine erscheint
[DisplayFromViewModel(nameof(…))] Beschriftung aus einer zweiten Eigenschaft des ViewModels; das Template rendert sie zusätzlich als verstecktes Feld
[UniqueId] Hängt ein Zufalls-Suffix an die Id, wenn dasselbe Formular mehrfach auf der Seite steht

Checkbox mit Pflicht-Ankreuzung

[CheckboxRequired] verlangt den Haken. Zwei Felder, einmal bool und einmal bool?, damit sichtbar wird, dass beide Typen gleich behandelt werden.

@model CheckboxPflichtBeispielViewModel

@* Zwei Pflicht-Checkboxen nebeneinander: oben ein bool, unten ein bool?. Absenden ohne Haken
   zeigt, dass CheckboxRequired beide Typen gleich behandelt. *@
<form asp-area="Basis" asp-controller="Checkbox" asp-action="Pflicht" method="post">
    @Html.EditorFor(m => m.IstAkzeptiert)
    @Html.EditorFor(m => m.IstGelesen)
    @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;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    /// <summary>
    /// Zwei Pflicht-Checkboxen, einmal als <c>bool</c> und einmal als <c>bool?</c>: Beide Typen
    /// verhalten sich gleich, unangekreuzt ist beides ungültig.
    /// </summary>
    public class CheckboxPflichtBeispielViewModel
    {
        [Display(Name = "Ich akzeptiere die Geschäftsbedingungen (AGB)")]
        [UIHint(UiEditor.Basis.Checkbox)]
        [CheckboxRequired(ErrorMessage = "Sie müssen die AGB akzeptieren.")]
        public bool IstAkzeptiert { get; set; }

        [Display(Name = "Ich habe die Datenschutzerklärung gelesen")]
        [UIHint(UiEditor.Basis.Checkbox)]
        [CheckboxRequired(ErrorMessage = "Bitte auch die Datenschutzerklärung bestätigen.")]
        public bool? IstGelesen { 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 (Checkbox); je eine Action/View pro Variante.
    /// Die POST-Actions zeigen den Unterschied: Ohne Pflichtangabe ist auch die leere Checkbox
    /// gültig, mit [CheckboxRequired] nicht.
    /// </summary>
    [Area("Basis")]
    public class CheckboxController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new CheckboxBeispielViewModel());
        }

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

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

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

        /// <summary>
        /// Gemeinsame PRG-Logik der 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 – Eingabe übernommen.", AlertType.Success, autoHide: true);

                // PRG-Pattern: nach erfolgreichem POST per Redirect auf die GET-Action.
                return this.RedirectToAction(action);
            }

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

Darstellung

Eine Checkbox, die angekreuzt sein muss, verlangt [CheckboxRequired] und nicht [Required]:

[Display(Name = "Ich akzeptiere die Geschäftsbedingungen (AGB)")]
[UIHint(UiEditor.Basis.Checkbox)]
[CheckboxRequired(ErrorMessage = "Sie müssen die AGB akzeptieren.")]
public bool IstAkzeptiert { get; set; }

Warum nicht [Required]? Ein bool hat mit false bereits einen Wert; [Required] ist damit erfüllt, auch wenn niemand angekreuzt hat. [CheckboxRequired] prüft stattdessen den Wert selbst: Alles, was nicht true ist, gilt als nicht angekreuzt, ein bool? mit null eingeschlossen. Genau deshalb zeigt das Beispiel beide Typen nebeneinander, sie verhalten sich gleich.

Drei Punkte, die man wissen sollte:

  • Geprüft wird serverseitig. CheckboxRequired bringt keinen unaufdringlichen Client-Adapter mit, die Meldung erscheint also nach dem Absenden. Der Tab «Controller (GET/POST)» zeigt das PRG-Muster dahinter: gültig führt zu einem Redirect, ungültig rendert dieselbe Seite mit den Meldungen.
  • Ohne ErrorMessage bleibt die Meldung leer. Das Attribut hat keine Standardmeldung aus einer Ressource; ErrorMessage oder ErrorMessageResourceType/-Name gehören deshalb immer gesetzt.
  • Die Beschriftung wird nicht ausgezeichnet. Die Klasse required-outline setzt das Template nur, wenn [Required], [RequiredIf] oder [RequiredIfContains] an der Eigenschaft hängt; HasRequiredAttribute() kennt CheckboxRequired nicht. Eine Pflicht-Checkbox sieht also aus wie eine freiwillige, bis die Meldung erscheint.

Das Attribut liegt im Paket FMH.Komponente.Basis.Validierung und hat dort eine eigene Demo-Seite mit allen Optionen: Basis.Validierung › Validierung › CheckboxRequired.