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 MvcConst.DropdownListSelection. *@

<form asp-area="FileUpload" asp-controller="DokumentListeDokumentTypBeispiel" asp-action="Index" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>
    @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
    {
        // 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.
        [UIHint("DokumentListe")]
        [Display(Name = "Belege")]
        [AdditionalMetadata(MvcConst.DropdownListSelection, nameof(DokumentListeViewModel.DokumentTypen))]
        [AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListeDokumentTypBeispiel/Upload")]
        [AdditionalMetadata(MvcConst.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(MvcConst.MaxFiles, "10")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadActionName, "Download")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadControllerName, "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
{
    // Zeigt die Dokumenttyp-Auswahl im Upload-Dialog. Die Liste dafür kommt aus der Eigenschaft
    // DokumentTypen des DokumentListeViewModel — benannt über MvcConst.DropdownListSelection am
    // ViewModel des Beispiels.
    [Area("FileUpload")]
    public class DokumentListeDokumentTypBeispielController : Controller
    {
        // Ablage der Demo: Dateiname und der im Dialog gewählte Dokumenttyp je vergebener Id.
        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;

        // 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.
        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("DokumentListe")]
[Display(Name = "Belege")]
[AdditionalMetadata(MvcConst.DropdownListSelection, nameof(DokumentListeViewModel.DokumentTypen))]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/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.