DokumentListe

Pflicht-Upload

Ohne mindestens ein zugeordnetes Dokument ist das Formular ungültig. Geprüft wird DokumentIds des DokumentListeViewModel.

@model DokumentListePflichtBeispielViewModel

@* Die Prüfung von DocumentUploadRequired läuft serverseitig — die Meldung erscheint also erst
   nach dem Absenden. Ohne asp-validation-summary bliebe sie unsichtbar. *@

<form asp-area="FileUpload" asp-controller="DokumentListePflichtBeispiel" asp-action="Index" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @Html.EditorFor(m => m.Nachweise)
    @Html.Fmh(Fmh.Button()
        .Text("Absenden")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-3" }))
</form>
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using FMH.Komponente.FileUpload.Models;
using FMH.Komponente.FileUpload.Ui.Validation.DataAnnotation;
using FMH.Komponente.Infrastruktur;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Models
{
    public class DokumentListePflichtBeispielViewModel
    {
        // DocumentUploadRequired sucht am ViewModel die erste Eigenschaft vom Typ
        // DokumentListeViewModel und prüft deren DokumentIds. Die Liste startet in diesem Beispiel
        // bewusst leer: Sind bereits Dokumente vorhanden, gilt die Bedingung als erfüllt und die
        // Meldung wäre nie zu sehen.
        [UIHint("DokumentListe")]
        [Display(Name = "Nachweis (mindestens ein Dokument)")]
        [DocumentUploadRequired]
        [AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListePflichtBeispiel/Upload")]
        [AdditionalMetadata(MvcConst.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(MvcConst.MaxFiles, "1")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadActionName, "Download")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadControllerName, "DokumentListePflichtBeispiel")]
        public DokumentListeViewModel Nachweise { get; set; } = new DokumentListeViewModel();
    }
}
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.WebUI.Areas.FileUpload.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Controllers.Beispiele
{
    // Zeigt [DocumentUploadRequired]. Die angezeigte Dokumentliste bleibt hier bewusst immer leer:
    // Sobald bereits Dokumente zugeordnet sind, gilt die Bedingung über das versteckte Feld
    // «NachweiseExistingDocuments» als erfüllt — die Pflichtmeldung wäre dann nicht mehr auslösbar
    // und das Beispiel nur einmal vorführbar.
    [Area("FileUpload")]
    public class DokumentListePflichtBeispielController : Controller
    {
        // Nur die Zuordnung Id -> Name für die Rückmeldung; die Einträge landen nicht in der Tabelle.
        private static readonly ConcurrentDictionary<int, string> Dokumente =
            new ConcurrentDictionary<int, string>();

        private static int letzteId;

        [HttpGet]
        public IActionResult Index()
        {
            return this.View(new DokumentListePflichtBeispielViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(DokumentListePflichtBeispielViewModel modell)
        {
            // Der ungültige Zweig ist hier der Lerninhalt: Ohne übertragenes Dokument ist
            // DokumentIds leer, DocumentUploadRequired meldet Document_Required und die View wird
            // mit dem Fehler erneut angezeigt. Die Prüfung läuft rein serverseitig — es gibt keinen
            // unaufdringlichen Client-Adapter dafür.
            if (this.ModelState.IsValid == false)
            {
                return this.View(modell);
            }

            var namen = modell.Nachweise.DokumentIds
                .Select(id => Dokumente.TryGetValue(id, out var name) ? name : $"Id {id}");

            this.AddAlert(
                $"Formular gültig – Nachweis vorhanden: {string.Join(", ", namen)}",
                AlertType.Success,
                autoHide: true);

            return this.RedirectToAction(nameof(this.Index));
        }

        /// <summary>
        /// Der Upload-Endpunkt dieses Beispiels. Antwort muss ein Json mit der Eigenschaft
        /// <c>id</c> sein — erst diese Id landet als verstecktes Feld <c>DokumentIds</c> im
        /// Formular und macht die Pflichtprüfung zufrieden.
        /// </summary>
        /// <returns>Json mit der vergebenen Id.</returns>
        [HttpPost]
        public IActionResult Upload()
        {
            if (this.Request.Headers.ContainsKey("IsDropzoneRequest") == false || this.Request.Form.Files.Count == 0)
            {
                return this.Json(new { id = 0 });
            }

            var datei = this.Request.Form.Files[0];
            var bezeichnung = this.Request.Form["DateiBezeichnung"].ToString();
            var name = string.IsNullOrWhiteSpace(bezeichnung) ? datei.FileName : bezeichnung;

            var id = Interlocked.Increment(ref letzteId);
            Dokumente[id] = name;

            return this.Json(new { id });
        }

        /// <summary>
        /// Der Download-Endpunkt. In diesem Beispiel bleibt die Tabelle leer, der Endpunkt ist
        /// dennoch konfiguriert, damit das ViewModel vollständig ist.
        /// </summary>
        /// <param name="id">Die Id des Dokuments.</param>
        /// <returns>Die Datei zum Herunterladen.</returns>
        [HttpGet]
        public IActionResult Download(int id)
        {
            if (Dokumente.TryGetValue(id, out var name) == false)
            {
                return this.NotFound();
            }

            var inhalt = Encoding.UTF8.GetBytes($"Platzhalter-Inhalt für «{name}» (Id {id}).");

            return this.File(inhalt, "text/plain", name);
        }
    }
}

Darstellung

attach_file

Drag files here or click here to upload.

report_problem

You can not upload any more files.

[DocumentUploadRequired] macht mindestens ein Dokument zur Pflicht.

[UIHint("DokumentListe")]
[Display(Name = "Nachweis (mindestens ein Dokument)")]
[DocumentUploadRequired]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListePflichtBeispiel/Upload")]
[AdditionalMetadata(MvcConst.MaxFiles, "1")]
public DokumentListeViewModel Nachweise { get; set; } = new DokumentListeViewModel();

Wie die Prüfung arbeitet

  • Sie sucht am ViewModel die erste Eigenschaft vom Typ DokumentListeViewModel und prüft deren DokumentIds. Ist die Liste leer oder null, ist das Formular ungültig; die Meldung stammt aus den Ressourcen (Document_Required).
  • Zusätzlich wertet sie das versteckte Feld <Feldname>ExistingDocuments aus, das das Template immer mitrendert: Sind bereits Dokumente zugeordnet (Bearbeitungsfall), gilt die Bedingung als erfüllt, auch wenn in diesem Durchgang nichts hochgeladen wurde. Dafür wird IHttpContextAccessor benötigt — in Program.cs mit AddHttpContextAccessor() registrieren.

Was zu beachten ist

  • Die Prüfung läuft serverseitig. Es gibt keinen unaufdringlichen Client-Adapter für data-val-documentuploadrequired; die Meldung erscheint also erst nach dem Absenden — eine asp-validation-summary im Formular macht sie sichtbar.
  • Enthält das ViewModel mehrere Eigenschaften vom Typ DokumentListeViewModel, gewinnt immer die erste. Für mehrere Pflichtlisten in einem Formular ist das Attribut daher nicht geeignet.
  • Zum Vorführen muss die Liste leer starten. Sind bereits Dokumente vorhanden, greift die ExistingDocuments-Abkürzung und die Meldung ist nicht mehr auslösbar.
  • Für eine bedingte Pflicht (nur, wenn ein anderes Feld einen bestimmten Wert hat) gibt es [DocumentUploadRequiredIf].