DatumBereich

Das DatumBereichAttribute validiert ein DateTime-Feld gegen einen Datumsbereich. Das DateTime-EditorTemplate liest den Bereich aus dem Attribut und setzt Min/Max am <input type="date">. Jedes Beispiel zeigt in Tabs die cshtml, das ViewModel und den Controller (GET/POST); darunter das funktionierende Formular. Ein Datum ausserhalb des Bereichs löst die Validierung aus.

Feste Daten (string, string)

Fester Bereich 01.01.2020 – 31.12.2030 über zwei Datums-Strings.

@model DatumBereichFestBeispielViewModel

<form asp-area="Basis" asp-controller="DatumBereich" asp-action="Fest" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @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;
using FMH.Komponente.Basis.Ui.Validation;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class DatumBereichFestBeispielViewModel
    {
        [Display(Name = "Datum (01.01.2020 – 31.12.2030)")]
        [DatumBereich("01.01.2020", "31.12.2030")]
        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
{
    // Ein Controller pro Attribut (DatumBereich); je eine Action/View pro Bereichs-Variante.
    // Die POST-Actions illustrieren die Validierungslogik: liegt das Datum im erlaubten Bereich,
    // ist ModelState.IsValid true.
    [Area("Basis")]
    public class DatumBereichController : Controller
    {
        [HttpGet]
        public IActionResult Fest()
        {
            return this.View(new DatumBereichFestBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Fest(DatumBereichFestBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: das Datum liegt im per [DatumBereich] erlaubten Bereich.
                // PRG-Pattern (Post-Redirect-Get): nach Erfolg per Redirect auf die GET-Action,
                // damit ein erneutes Laden das Formular nicht nochmals abschickt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Fest));
            }

            // Ungültig: Datum ausserhalb des Bereichs -> dieselbe View mit den Validierungsfehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Offset()
        {
            return this.View(new DatumBereichOffsetBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Tage()
        {
            return this.View(new DatumBereichTageBeispielViewModel());
        }

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

            return this.View(modell);
        }
    }
}

Darstellung

Das DatumBereichAttribute validiert ein DateTime-Feld gegen einen Bereich. Mit zwei Datums-Strings (Format dd.MM.yyyy, de-CH) werden feste Grenzen gesetzt. Das DateTime-EditorTemplate liest den Bereich aus dem Attribut und begrenzt den <input type="date"> entsprechend; server- und clientseitig (unobtrusive) wird geprüft.

[Display(Name = "Datum")]
[DatumBereich("01.01.2020", "31.12.2030")]
public DateTime? Datum { get; set; }

Ein leeres Feld ist gültig (das Attribut prüft nur bei vorhandenem Wert); für ein Pflichtfeld zusätzlich [Required] setzen.

Konstruktoren (Bereich)

Überladung Bereich
[DatumBereich] 01.01.1970 bis 31.12. (heute + 10 Jahre)
[DatumBereich("01.01.2020", "31.12.2030")] feste Daten, Format dd.MM.yyyy (de-CH)
[DatumBereich("01.01.2000", 5)] Minimum fest, Maximum = heute + 5 Jahre (siehe «Jahr-Offset»)
[DatumBereich(-30, 30)] relativ zu heute in Tagen (siehe «Relative Tage»)

Fehlermeldung

Ohne eigene ErrorMessage erscheint die lokalisierte Standardmeldung _FmhWebMvc.ValidationDateTimeRange mit eingesetztem Min-/Max-Datum. Für eine eigene Ressourcen-Meldung ErrorMessageResourceType/-Name setzen; die Platzhalter für die Grenzen sind dort {{0}} (Min) und {{1}} (Max), da die Meldung zweistufig formatiert wird.

Jahr-Offset (string, int)

Minimum fest, Maximum = heute + 5 Jahre.

@model DatumBereichOffsetBeispielViewModel

<form asp-area="Basis" asp-controller="DatumBereich" asp-action="Offset" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @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;
using FMH.Komponente.Basis.Ui.Validation;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class DatumBereichOffsetBeispielViewModel
    {
        [Display(Name = "Datum (01.01.2000 bis heute + 5 Jahre)")]
        [DatumBereich("01.01.2000", 5)]
        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
{
    // Ein Controller pro Attribut (DatumBereich); je eine Action/View pro Bereichs-Variante.
    // Die POST-Actions illustrieren die Validierungslogik: liegt das Datum im erlaubten Bereich,
    // ist ModelState.IsValid true.
    [Area("Basis")]
    public class DatumBereichController : Controller
    {
        [HttpGet]
        public IActionResult Fest()
        {
            return this.View(new DatumBereichFestBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Fest(DatumBereichFestBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: das Datum liegt im per [DatumBereich] erlaubten Bereich.
                // PRG-Pattern (Post-Redirect-Get): nach Erfolg per Redirect auf die GET-Action,
                // damit ein erneutes Laden das Formular nicht nochmals abschickt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Fest));
            }

            // Ungültig: Datum ausserhalb des Bereichs -> dieselbe View mit den Validierungsfehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Offset()
        {
            return this.View(new DatumBereichOffsetBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Tage()
        {
            return this.View(new DatumBereichTageBeispielViewModel());
        }

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

            return this.View(modell);
        }
    }
}

Darstellung

Mit [DatumBereich(string minDatum, int maxJahrOffset)] ist das Minimum fest und das Maximum das heutige Datum plus die angegebenen Jahre (Uhrzeit 23:59:59). Bei negativem Offset wird ein Tag addiert (Schaltjahr-Behandlung 29. Februar).

[Display(Name = "Datum")]
[DatumBereich("01.01.2000", 5)]
public DateTime? Datum { get; set; }

Relative Tage (int, int)

Beide Grenzen relativ zu heute: -30 bis +30 Tage.

@model DatumBereichTageBeispielViewModel

<form asp-area="Basis" asp-controller="DatumBereich" asp-action="Tage" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @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;
using FMH.Komponente.Basis.Ui.Validation;

namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
    public class DatumBereichTageBeispielViewModel
    {
        [Display(Name = "Datum (heute -30 bis +30 Tage)")]
        [DatumBereich(-30, 30)]
        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
{
    // Ein Controller pro Attribut (DatumBereich); je eine Action/View pro Bereichs-Variante.
    // Die POST-Actions illustrieren die Validierungslogik: liegt das Datum im erlaubten Bereich,
    // ist ModelState.IsValid true.
    [Area("Basis")]
    public class DatumBereichController : Controller
    {
        [HttpGet]
        public IActionResult Fest()
        {
            return this.View(new DatumBereichFestBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Fest(DatumBereichFestBeispielViewModel modell)
        {
            if (this.ModelState.IsValid)
            {
                // Gültig: das Datum liegt im per [DatumBereich] erlaubten Bereich.
                // PRG-Pattern (Post-Redirect-Get): nach Erfolg per Redirect auf die GET-Action,
                // damit ein erneutes Laden das Formular nicht nochmals abschickt.
                this.AddAlert("Formular ist gültig – Validierung erfolgreich.", AlertType.Success, autoHide: true);
                return this.RedirectToAction(nameof(this.Fest));
            }

            // Ungültig: Datum ausserhalb des Bereichs -> dieselbe View mit den Validierungsfehlern.
            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Offset()
        {
            return this.View(new DatumBereichOffsetBeispielViewModel());
        }

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

            return this.View(modell);
        }

        [HttpGet]
        public IActionResult Tage()
        {
            return this.View(new DatumBereichTageBeispielViewModel());
        }

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

            return this.View(modell);
        }
    }
}

Darstellung

Mit [DatumBereich(int minTageVomAktuellenDatum, int maxDatumTage)] sind beide Grenzen relativ zum heutigen Datum in Tagen; negative Werte für die Vergangenheit sind möglich.

[Display(Name = "Datum")]
[DatumBereich(-30, 30)]
public DateTime? Datum { get; set; }

Hinweis: Die relativen Grenzen werden bei der Instanzierung des Attributs mit DateTime.Now berechnet. Da der Validator Attribut-Instanzen cached, wandert der Bereich in einer lang laufenden Anwendung nicht pro Request mit.