DokumentListe

Dokumenttyp im Dialog

Der Dialog verlangt neben der Bezeichnung einen Dokumenttyp; die Auswahl kommt aus der Eigenschaft DokumentTypen.

@model DokumentListeDokumentTypBeispielViewModel

@* Die Auswahl des Dokumenttyps erscheint im Dialog, der nach dem Ablegen einer Datei aufgeht.
   Gefüllt wird sie aus der Eigenschaft DokumentTypen des DokumentListeViewModel, der
   Controller setzt sie, das ViewModel benennt sie über UiMetadata.Basis.DropdownListSelection. *@

<form asp-area="FileUpload" asp-controller="DokumentListeDokumentTypBeispiel" asp-action="Index" method="post">
    @Html.EditorFor(m => m.Belege)
    @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.Infrastruktur;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Models
{
    public class DokumentListeDokumentTypBeispielViewModel
    {
        /// <summary>
        /// DropdownListSelection nennt eine Eigenschaft des DokumentListeViewModel (nicht dieses
        /// ViewModels): "DokumentTypen" wird zur Laufzeit per Reflexion daraus gelesen und füllt die
        /// Auswahl im Upload-Dialog. Die Auswahl kommt als Formularfeld "DokumentTyp" am Endpunkt an.
        /// </summary>
        [UIHint(UiEditor.FileUpload.DokumentListe)]
        [Display(Name = "Belege")]
        [AdditionalMetadata(UiMetadata.Basis.DropdownListSelection, nameof(DokumentListeViewModel.DokumentTypen))]
        [AdditionalMetadata(UiMetadata.FileUpload.UploadUrl, "/FileUpload/DokumentListeDokumentTypBeispiel/Upload")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFiles, "10")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadActionName, "Download")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadControllerName, "DokumentListeDokumentTypBeispiel")]
        public DokumentListeViewModel Belege { get; set; } = new DokumentListeViewModel();
    }
}
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.FileUpload.Models;
using FMH.Komponente.WebUI.Areas.FileUpload.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Controllers.Beispiele
{
    /// <summary>
    /// Zeigt die Dokumenttyp-Auswahl im Upload-Dialog. Die Liste dafür kommt aus der Eigenschaft
    /// DokumentTypen des DokumentListeViewModel, benannt über UiMetadata.Basis.DropdownListSelection am
    /// ViewModel des Beispiels.
    /// </summary>
    [Area("FileUpload")]
    public class DokumentListeDokumentTypBeispielController : Controller
    {
        /// <summary>
        /// Ablage der Demo: Dateiname und der im Dialog gewählte Dokumenttyp je vergebener Id.
        /// </summary>
        private static readonly ConcurrentDictionary<int, (string Name, string Typ)> Dokumente =
            new ConcurrentDictionary<int, (string Name, string Typ)>(
                new Dictionary<int, (string Name, string Typ)>
                {
                    [5] = ("Kurs.pdf", "2"),
                    [6] = ("Kurs2.pdf", "8"),
                });

        private static int letzteId = 100;

        /// <summary>
        /// Die Typenliste gehört an das DokumentListeViewModel, nicht an das äussere ViewModel,
        /// das Template liest sie über den in DropdownListSelection genannten Eigenschaftsnamen.
        /// Auch die Doku-Seite zeigt die gefüllte Liste, deshalb öffentlich.
        /// </summary>
        /// <returns>Das Modell mit der Ausgangslage des Beispiels.</returns>
        public static DokumentListeDokumentTypBeispielViewModel ErstelleModell()
        {
            var modell = new DokumentListeDokumentTypBeispielViewModel();

            modell.Belege.Dokumente = Dokumente
                .OrderBy(eintrag => eintrag.Key)
                .Select(eintrag => new DokumentEintrag
                                   {
                                       DokumentEintragId = eintrag.Key,
                                       Name = eintrag.Value.Name,
                                       Groesse = 30000 + (eintrag.Key * 100),
                                   })
                .ToList();

            modell.Belege.DokumentTypen = new List<SelectListItem>
                                          {
                                              new SelectListItem { Value = "2", Text = "Entscheid" },
                                              new SelectListItem { Value = "4", Text = "Einsprache" },
                                              new SelectListItem { Value = "8", Text = "Abschluss" },
                                          };

            return modell;
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(DokumentListeDokumentTypBeispielViewModel modell)
        {
            var namen = (modell.Belege.DokumentIds ?? new int[0]).Select(BeschreibeDokument);

            this.AddAlert(
                modell.Belege.DokumentIds == null || modell.Belege.DokumentIds.Length == 0
                    ? "Formular übertragen – keine neuen Dokumente."
                    : $"Formular übertragen – neu: {string.Join(", ", namen)}",
                AlertType.Success,
                autoHide: true);

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

        /// <summary>
        /// Der Upload-Endpunkt dieses Beispiels. Neben der Datei und der Bezeichnung kommt hier
        /// zusätzlich das Formularfeld <c>DokumentTyp</c> mit, der Wert der Auswahl aus dem Dialog.
        /// </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;

            // Der Dialog erzwingt die Auswahl, sobald eine Typenliste konfiguriert ist.
            var typ = this.Request.Form["DokumentTyp"].ToString();

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

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

        /// <summary>
        /// Der Download-Endpunkt dieses Beispiels.
        /// </summary>
        /// <param name="id">Die Id des Dokuments aus der Tabellenzeile.</param>
        /// <returns>Die Datei zum Herunterladen.</returns>
        [HttpGet]
        public IActionResult Download(int id)
        {
            if (Dokumente.TryGetValue(id, out var eintrag) == false)
            {
                return this.NotFound();
            }

            var inhalt = Encoding.UTF8.GetBytes($"Platzhalter-Inhalt für «{eintrag.Name}» (Typ {eintrag.Typ}).");

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

        private static string BeschreibeDokument(int id)
        {
            return Dokumente.TryGetValue(id, out var eintrag)
                       ? $"{eintrag.Name} (Typ {eintrag.Typ})"
                       : $"Id {id}";
        }
    }
}

Darstellung

Description Size Actions
Kurs.pdf 29.79 KB
Kurs2.pdf 29.88 KB
attach_file

Drag files here or click here to upload.

report_problem

You can not upload any more files.

Ist eine Typenliste konfiguriert, verlangt der Dialog nach dem Ablegen einer Datei neben der Bezeichnung zusätzlich einen Dokumenttyp.

[UIHint(UiEditor.FileUpload.DokumentListe)]
[Display(Name = "Belege")]
[AdditionalMetadata(UiMetadata.Basis.DropdownListSelection, nameof(DokumentListeViewModel.DokumentTypen))]
[AdditionalMetadata(UiMetadata.FileUpload.UploadUrl, "/FileUpload/DokumentListeDokumentTypBeispiel/Upload")]
public DokumentListeViewModel Belege { get; set; } = new DokumentListeViewModel();

Die Liste selbst setzt der Controller am DokumentListeViewModel, nicht am äusseren ViewModel:

modell.Belege.DokumentTypen = new List<SelectListItem>
{
    new SelectListItem { Value = "2", Text = "Entscheid" },
    new SelectListItem { Value = "4", Text = "Einsprache" },
    new SelectListItem { Value = "8", Text = "Abschluss" },
};

Im Upload-Endpunkt kommt die Auswahl als eigenes Formularfeld an:

var typ = this.Request.Form["DokumentTyp"].ToString();

Was zu beachten ist

  • Der Wert von DropdownListSelection ist ein Eigenschaftsname am DokumentListeViewModel, er wird zur Laufzeit per Reflexion gelesen. Ein Tippfehler führt zu einer Ausnahme beim Rendern, nicht zu einer stillen leeren Liste. nameof(DokumentListeViewModel.DokumentTypen) schützt davor.
  • Ist die Liste leer, entfällt die Auswahl im Dialog ersatzlos, das Feld verhält sich dann wie im Grundfall.
  • Solange kein Typ gewählt ist, bleibt die Schaltfläche zum Bestätigen im Dialog gesperrt: Die Auswahl ist damit ein Pflichtfeld des Dialogs.
  • Der Typ gehört zur einzelnen Datei, nicht zum Feld. Er wird deshalb je Upload übertragen und muss im Endpunkt zusammen mit der Datei gespeichert werden, im späteren Formular-POST ist er nicht mehr enthalten.