Datum
Das DateTime-EditorTemplate rendert ein DateTime? als Datumsfeld.
Einbindung über @Html.EditorFor(...) mit [UIHint(UiEditor.Basis.DateTime)].
Datum (Grundfall)
Ein DateTime? als Datumsfeld. Das Feld ist ein input vom Typ date, den Kalender bringt der Browser mit.
@model DatumBeispielViewModel
<form asp-area="Basis" asp-controller="Datum" asp-action="Index" method="post">
@Html.EditorFor(m => m.Datum)
@Html.Fmh(Fmh.Button()
.Text("Absenden")
.Size(ButtonSize.Sm)
.Attributes(new { @class = "mt-2" }))
</form>
using System;
using System.ComponentModel.DataAnnotations;
namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
/// <summary>
/// Der Grundfall: ein Datumsfeld ohne Pflichtangabe und ohne Bereichsgrenzen.
/// </summary>
public class DatumBeispielViewModel
{
[Display(Name = "Datum")]
[UIHint(UiEditor.Basis.DateTime)]
public DateTime? Datum { 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 (DateTime); je eine Action/View pro Variante.
/// Die POST-Actions zeigen, was der Server aus der Eingabe macht: ein leeres Feld ist ohne
/// Pflichtangabe gültig, mit [Required] nicht, und ein unlesbares Datum scheitert schon beim
/// Binden.
/// </summary>
[Area("Basis")]
public class DatumController : Controller
{
[HttpGet]
public IActionResult Index()
{
return this.View(new DatumBeispielViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(DatumBeispielViewModel modell)
{
return this.PruefeUndZeige(modell, nameof(this.Index));
}
[HttpGet]
public IActionResult Pflicht()
{
return this.View(new DatumPflichtBeispielViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Pflicht(DatumPflichtBeispielViewModel 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 – Datum ü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 DateTime-EditorTemplate rendert ein DateTime? als Datumsfeld. Eingebunden wird es über den ASP.NET-Core-Standardmechanismus, also [UIHint(UiEditor.Basis.DateTime)] am Modell und Html.EditorFor(...) in der View, innerhalb eines Formulars.
[Display(Name = "Datum")]
[UIHint(UiEditor.Basis.DateTime)]
public DateTime? Datum { get; set; }
@Html.EditorFor(m => m.Datum)
Der Kalender kommt vom Browser. Gerendert wird ein <input type="date">; Aussehen und Bedienung des Auswahlfelds bestimmt also der Browser, ebenso das angezeigte Format. Über die Leitung geht der Wert immer als yyyy-MM-dd. Ein Platzhalter mit dem heutigen Datum in dieser Schreibweise steht am Feld, und autocomplete ist abgeschaltet.
DateTime? und nicht DateTime: Nur mit dem nullbaren Typ gibt es ein leeres Feld; ein DateTime käme mit dem 01.01.0001 an.
Die Attribute im Überblick
| Attribut | Wirkung |
|---|---|
[UIHint(UiEditor.Basis.DateTime)] |
Wählt die Vorlage; Standard-MVC-Mechanik, die Konstante kommt aus UiEditor.Basis (FMH.Komponente.Basis.Validierung) |
[Display(Name = "…")] |
Beschriftung des Felds |
[Required] |
Pflichtangabe, siehe Beispiel «Datum mit Pflichtangabe» |
[DatumBereich] |
Grenzt den erlaubten Bereich ein; ohne Argumente gilt 01.01.1970 bis 31.12. des Jahres «heute + 10 Jahre». Eigene Seite: Basis.Validierung › Validierung › DatumBereich |
[HelpText(Name = "…")] |
Hinweis unter dem Feld; er weicht der Fehlermeldung, sobald eine erscheint |
[DisplayFromViewModel(nameof(…))] |
Beschriftung aus einer zweiten Eigenschaft des ViewModels |
[UniqueId] |
Hängt ein Zufalls-Suffix an die Id, wenn dasselbe Formular mehrfach auf der Seite steht |
Dazu kommen die üblichen Attribute aus System.ComponentModel.DataAnnotations, sie gelten hier wie an jeder anderen Eigenschaft.
Was zu beachten ist
minundmaxsetzt das Template nicht. Es liest die Grenzen aus einem vorhandenen[DatumBereich]zwar aus, schreibt sie aber nicht an das<input>. Der Browser lässt also jedes Datum zu; geprüft wird über die unaufdringliche Validierung des Attributs und beim Absenden auf dem Server.- Eine unlesbare Eingabe scheitert vor der Prüfung. Was der Modell-Binder nicht in ein
DateTimeumwandeln kann, landet als Bindungsfehler imModelState; die Meldung dafür liefertdata-val-date, entweder aus der eigenen[Required]-Meldung oder aus_FmhWebMvc.ValidationDateTime. - Das Template bringt ein eigenes Skript mit, das die Prüfung an das
change-Ereignis hängt (fmhMDCvalidiere). Ohne das Skript-Bundle der Basis meldet das Feld erst beim Absenden.
Datum mit Pflichtangabe
[Required] macht das Feld zur Pflicht, [HelpText] ergänzt einen Hinweis darunter. Absenden mit leerem Feld zeigt die Meldung.
@model DatumPflichtBeispielViewModel
<form asp-area="Basis" asp-controller="Datum" asp-action="Pflicht" method="post">
@Html.EditorFor(m => m.Geburtstag)
@Html.Fmh(Fmh.Button()
.Text("Absenden")
.Size(ButtonSize.Sm)
.Attributes(new { @class = "mt-2" }))
</form>
using System;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
/// <summary>
/// Pflichtangabe mit Hilfetext: Beides sind Standard-Bausteine, die jedes Eingabe-Template
/// dieser Sammlung versteht.
/// </summary>
public class DatumPflichtBeispielViewModel
{
[Display(Name = "Geburtstag")]
[UIHint(UiEditor.Basis.DateTime)]
[Required(ErrorMessage = "Bitte den Geburtstag angeben.")]
[HelpText(Name = "Der Browser zeigt seinen eigenen Kalender; die Eingabe über die Tastatur geht ebenso.")]
public DateTime? Geburtstag { 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 (DateTime); je eine Action/View pro Variante.
/// Die POST-Actions zeigen, was der Server aus der Eingabe macht: ein leeres Feld ist ohne
/// Pflichtangabe gültig, mit [Required] nicht, und ein unlesbares Datum scheitert schon beim
/// Binden.
/// </summary>
[Area("Basis")]
public class DatumController : Controller
{
[HttpGet]
public IActionResult Index()
{
return this.View(new DatumBeispielViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(DatumBeispielViewModel modell)
{
return this.PruefeUndZeige(modell, nameof(this.Index));
}
[HttpGet]
public IActionResult Pflicht()
{
return this.View(new DatumPflichtBeispielViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Pflicht(DatumPflichtBeispielViewModel 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 – Datum ü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
[Required] macht die Angabe zur Pflicht, [HelpText] ergänzt einen Hinweis unter dem Feld:
[Display(Name = "Geburtstag")]
[UIHint(UiEditor.Basis.DateTime)]
[Required(ErrorMessage = "Bitte den Geburtstag angeben.")]
[HelpText(Name = "Der Browser zeigt seinen eigenen Kalender; die Eingabe über die Tastatur geht ebenso.")]
public DateTime? Geburtstag { get; set; }
Was dabei zu sehen ist:
- Die Beschriftung wird ausgezeichnet. Bei Pflichtangabe hängt das Template die Klasse
requiredan das Label undrequiredan das Eingabefeld selbst, die Kennzeichnung kommt also ohne Zutun der Seite. - Der Hilfetext tritt zurück, sobald eine Fehlermeldung erscheint, und kommt danach wieder.
- Geprüft wird server- und clientseitig. 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.
Soll zusätzlich der Bereich eingegrenzt werden, etwa ein Geburtsdatum zwischen 1950 und heute, kommt [DatumBereich("01.01.1950", 0)] dazu. Der zweite Wert ist ein Jahres-Offset auf das heutige Datum, 0 heisst also «nicht in der Zukunft», -18 entsprechend «vor mindestens 18 Jahren». Alle drei Überladungen mit lauffähigen Beispielen stehen unter Basis.Validierung › Validierung › DatumBereich.