DokumentListe

Download-Url zur Laufzeit

Routen, die sich nicht über die Data Annotationen abbilden lassen, werden als Vorlage mit dem Platzhalter {dokumentId} gesetzt.

@model DokumentListeDownloadUrlBeispielViewModel

@* Das Skript überschreibt die Download-Adresse zur Laufzeit. Es steht am Ende des Partials und
   wartet zusätzlich auf "load": window.Belege entsteht erst im ready-Block des Templates, der
   über die BodyIncludes des Layouts nach diesem Partial gerendert wird. *@

<form asp-area="FileUpload" asp-controller="DokumentListeDownloadUrlBeispiel" 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>

@Html.PostScript(@<script src="~/js/demos/dokumentliste-downloadurl.js" asp-append-version="true"></script>)
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 DokumentListeDownloadUrlBeispielViewModel
    {
        /// <summary>
        /// Die hier eingetragene Download-Adresse ist nur der Ausgangspunkt: Das Skript des
        /// Beispiels überschreibt sie zur Laufzeit über window.Belege.options.dokumentDownloadUrl.
        /// Der Eigenschaftsname "Belege" ist zugleich der Name der globalen Dropzone.
        /// </summary>
        [UIHint(UiEditor.FileUpload.DokumentListe)]
        [Display(Name = "Dossier-Belege")]
        [AdditionalMetadata(UiMetadata.FileUpload.UploadUrl, "/FileUpload/DokumentListeDownloadUrlBeispiel/Upload")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(UiMetadata.FileUpload.MaxFiles, "5")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadActionName, "Download")]
        [AdditionalMetadata(UiMetadata.FileUpload.DownloadControllerName, "DokumentListeDownloadUrlBeispiel")]
        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;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Controllers.Beispiele
{
    /// <summary>
    /// Zeigt eine Download-Route, die sich nicht über MetaDataDownloadActionName /
    /// MetaDataDownloadControllerName abbilden lässt, weil sie einen zusätzlichen Wegabschnitt
    /// (die Dossier-Nummer) enthält. Das Skript des Partials setzt sie deshalb zur Laufzeit.
    /// </summary>
    [Area("FileUpload")]
    public class DokumentListeDownloadUrlBeispielController : Controller
    {
        private static readonly ConcurrentDictionary<int, string> Dokumente =
            new ConcurrentDictionary<int, string>(
                new Dictionary<int, string> { [5] = "Kurs.pdf", [6] = "Kurs2.pdf" });

        private static int letzteId = 100;

        /// <summary>
        /// Auch die Doku-Seite zeigt die gefüllte Liste, deshalb öffentlich.
        /// </summary>
        /// <returns>Das Modell mit der Ausgangslage des Beispiels.</returns>
        public static DokumentListeDownloadUrlBeispielViewModel ErstelleModell()
        {
            var modell = new DokumentListeDownloadUrlBeispielViewModel();

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

            return modell;
        }

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(DokumentListeDownloadUrlBeispielViewModel modell)
        {
            var anzahlNeu = modell.Belege.DokumentIds?.Length ?? 0;

            this.AddAlert(
                $"Formular übertragen – {anzahlNeu} neue(s) Dokument(e).",
                AlertType.Success,
                autoHide: true);

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

        /// <summary>
        /// Der Upload-Endpunkt dieses Beispiels.
        /// </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, den die Data Annotationen erzeugen. Er dient hier nur als
        /// Ausgangswert, geklickt wird die Adresse aus <c>dokumentDownloadUrl</c>.
        /// </summary>
        /// <param name="id">Die Id des Dokuments aus der Tabellenzeile.</param>
        /// <returns>Die Datei zum Herunterladen.</returns>
        [HttpGet]
        public IActionResult Download(int id)
        {
            return this.LiefereDatei(id, dossierId: 0);
        }

        /// <summary>
        /// Die eigentliche Ziel-Route dieses Beispiels: Sie trägt die Dossier-Nummer im Weg und
        /// lässt sich deshalb nicht über die Data Annotationen erzeugen. Das Skript setzt sie über
        /// <c>window.Belege.options.dokumentDownloadUrl</c> mit dem Platzhalter
        /// <c>{dokumentId}</c>.
        /// </summary>
        /// <param name="dossierId">Die Nummer des Dossiers.</param>
        /// <param name="id">Die Id des Dokuments.</param>
        /// <returns>Die Datei zum Herunterladen.</returns>
        [HttpGet]
        [Route("FileUpload/DokumentListeDownloadUrlBeispiel/Dossier/{dossierId:int}/Download/{id:int}")]
        public IActionResult DossierDownload(int dossierId, int id)
        {
            return this.LiefereDatei(id, dossierId);
        }

        private IActionResult LiefereDatei(int id, int dossierId)
        {
            if (Dokumente.TryGetValue(id, out var name) == false)
            {
                return this.NotFound();
            }

            var herkunft = dossierId == 0 ? "ohne Dossier" : $"Dossier {dossierId}";
            var inhalt = Encoding.UTF8.GetBytes($"Platzhalter-Inhalt für «{name}» (Id {id}, {herkunft}).");

            return this.File(inhalt, "text/plain", name);
        }
    }
}
// Beispiel «Download-Url zur Laufzeit» (Area FileUpload, EditorTemplate DokumentListe).
// Lässt sich eine Download-Route nicht über MetaDataDownloadActionName/-ControllerName abbilden
// (hier: eine Dossier-Nummer mitten im Weg), wird sie als Vorlage an der Dropzone hinterlegt. Beim
// Klick auf einen Download-Link ersetzt das Template darin den Platzhalter {dokumentId}.
(function () {
    "use strict";

    // In einem Projekt käme dieser Wert aus der Seite; hier steht er stellvertretend fest.
    var dossierId = 25;

    // window.Belege ist die Dropzone der Eigenschaft "Belege" und entsteht erst im ready-Block des
    // Templates. "load" läuft garantiert danach.
    window.addEventListener("load", function () {
        if (typeof window.Belege === "undefined") {
            return;
        }

        window.Belege.options.dokumentDownloadUrl =
            "/FileUpload/DokumentListeDownloadUrlBeispiel/Dossier/" + dossierId + "/Download/{dokumentId}";
    });
})();
// Dieselbe Umsetzung mit jQuery. Diese Datei wird bewusst nicht geladen, sie dient nur dem
// Code-Tab «jQuery», sonst liefen beide Varianten gleichzeitig.
$(window).on("load", function () {
    var dossierId = 25;

    if (typeof window.Belege === "undefined") {
        return;
    }

    // $(document).ready reicht hier nicht: Die Dropzone wird selbst in einem ready-Block angelegt,
    // und der des Templates läuft nach diesem hier. "load" ist der sichere Zeitpunkt.
    window.Belege.options.dokumentDownloadUrl =
        "/FileUpload/DokumentListeDownloadUrlBeispiel/Dossier/" + dossierId + "/Download/{dokumentId}";
});

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.

Lässt sich die Download-Route nicht über MetaDataDownloadActionName und MetaDataDownloadControllerName abbilden, etwa weil sie einen zusätzlichen Wegabschnitt enthält, wird sie als Vorlage an der Dropzone hinterlegt. Beim Klick auf einen Dateinamen ersetzt das Template darin den Platzhalter {dokumentId} durch die Id der jeweiligen Zeile.

window.addEventListener("load", function () {
    var dossierId = 25;
    window.Belege.options.dokumentDownloadUrl =
        "/FileUpload/DokumentListeDownloadUrlBeispiel/Dossier/" + dossierId + "/Download/{dokumentId}";
});

Wie die Dropzone heisst

window.Belege ist die Dropzone der Eigenschaft Belege. Der Name entspricht der Html-Id, also dem Eigenschaftsnamen. Trägt die Eigenschaft [UniqueId], enthält der Name zusätzlich ein Zufalls-Suffix, dann ist dieser Weg nicht mehr vorhersagbar.

Was zu beachten ist

  • Der Zeitpunkt ist entscheidend: window.<Feldname> entsteht erst im ready-Block des Templates, der über die BodyIncludes des Layouts nach dem Beispiel-Partial gerendert wird. Ein $(document).ready im Partial läuft davor und fände die Dropzone noch nicht. load ist der sichere Zeitpunkt.
  • Der Platzhalter heisst exakt {dokumentId}. Fehlt er in der Vorlage, zeigen alle Zeilen auf dieselbe Adresse.
  • Ist dokumentDownloadUrl nicht gesetzt, bleibt der aus den Data Annotationen gebildete Link unverändert bestehen, die Vorlage ist reine Ergänzung.
  • Die Ersetzung passiert beim Klick, nicht beim Rendern. Die Vorlage darf also erst nach dem Seitenaufbau feststehen; das ist gerade ihr Zweck.