DocumentUploadRequired

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 – beim Feld, wie jede andere Meldung auch. *@

<form asp-area="FileUploadValidierung" asp-controller="DokumentListePflichtBeispiel" asp-action="Index" method="post">
    @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.FileUploadValidierung.Models
{
    public class DokumentListePflichtBeispielViewModel
    {
        /// <summary>
        /// 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.
        /// </summary>
        [UIHint(UiEditor.FileUpload.DokumentListe)]
        [Display(Name = "Nachweis (mindestens ein Dokument)")]
        [DocumentUploadRequired]
        [AdditionalMetadata(UiMetadata.FileUpload.UploadUrl, "/FileUploadValidierung/DokumentListePflichtBeispiel/Upload")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFiles, "1")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadActionName, "Download")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadControllerName, "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.FileUploadValidierung.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.FileUploadValidierung.Controllers.Beispiele
{
    /// <summary>
    /// 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.
    /// </summary>
    [Area("FileUploadValidierung")]
    public class DokumentListePflichtBeispielController : Controller
    {
        /// <summary>
        /// Nur die Zuordnung Id -&gt; Name für die Rückmeldung; die Einträge landen nicht in der Tabelle.
        /// </summary>
        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(UiEditor.FileUpload.DokumentListe)]
[Display(Name = "Nachweis (mindestens ein Dokument)")]
[DocumentUploadRequired]
[AdditionalMetadata(UiMetadata.FileUpload.UploadUrl, "/FileUpload/DokumentListePflichtBeispiel/Upload")]
[AdditionalMetadata(UiMetadata.FileUpload.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 FileUploadValidierungResources.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, die Registrierung steht unter Vorbedingungen.

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, dann aber beim Feld wie jede andere. Eine asp-validation-summary braucht es dafür nicht.
  • 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].