IFormFile

Maximale Anzahl Dateien (MaxUploadableFiles)

Obergrenze aus einer Eigenschaft des ViewModels statt aus einer Konstanten – mit eigener Meldung, sobald das Kontingent ausgeschöpft ist.

@model IFormFileMaxDateienBeispielViewModel

@* Die Obergrenze kommt aus der Eigenschaft VerfuegbarerUpload des ViewModels. Wird sie erreicht,
   erscheint die eigene Meldung aus dem Attribut statt der Standardmeldung. *@

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

namespace FMH.Komponente.WebUI.Areas.FileUpload.Models
{
    public class IFormFileMaxDateienBeispielViewModel
    {
        [Display(Name = "Beilagen")]
        [UIHint("IFormFile")]
        [AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/IFormFileMaxDateienBeispiel/Upload")]
        [MaxUploadableFiles(
            MaxFilesProperty = nameof(VerfuegbarerUpload),
            ErrorMessage = "Für diesen Antrag sind noch zwei Beilagen möglich.")]
        public IFormFile Beilage { get; set; }

        // Die Zahl kommt zur Laufzeit aus dem Modell (z. B. aus dem bereits vorhandenen Bestand)
        // statt als fester Wert im Attribut. Das Template schreibt sie zusätzlich als verstecktes
        // Feld ins Formular, damit sie den Round-Trip übersteht.
        public int VerfuegbarerUpload { get; set; } = 2;

        [UIHint("HiddenList")]
        public List<int> BeilageIds { get; set; } = new List<int>();
    }
}
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
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
{
    // Ein Beispiel, ein Controller: Upload-Endpunkt, Ablage und Formular-Round-Trip stehen
    // vollständig in dieser Datei. Lerninhalt hier: die Obergrenze kommt aus einer Eigenschaft des
    // ViewModels ([MaxUploadableFiles]) — der Controller selbst bleibt der übliche PRG-Rumpf.
    [Area("FileUpload")]
    public class IFormFileMaxDateienBeispielController : Controller
    {
        // Ablage der Demo: In einem Projekt landen die Dateien in der Datenbank oder im
        // Dokumentenspeicher; hier genügt der Dateiname je vergebener Id, damit sich der
        // Round-Trip nachvollziehen lässt. Statisch, weil die Demo keine Sitzung kennt.
        private static readonly ConcurrentDictionary<int, string> Dokumente = new ConcurrentDictionary<int, string>();

        private static int letzteId;

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

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(IFormFileMaxDateienBeispielViewModel modell)
        {
            // PRG-Pattern (Post-Redirect-Get): Meldung merken und per Redirect auf die GET-Action.
            this.AddAlert($"Formular übertragen – {this.BeschreibeUpload(modell.BeilageIds)}", AlertType.Success, autoHide: true);
            return this.RedirectToAction(nameof(this.Index));
        }

        /// <summary>
        /// Der Upload-Endpunkt dieses Beispiels; seine Adresse steht als
        /// <see cref="FMH.Komponente.Infrastruktur.MvcConst.MetaDataUploadUrl" /> am ViewModel.
        /// Die Dropzone schickt jede Datei einzeln hierher, sobald der Dialog bestätigt wurde.
        /// Antwort muss ein Json mit der Eigenschaft <c>id</c> sein — das Template legt daraus ein
        /// verstecktes Feld <c>&lt;Feldname&gt;Ids</c> im Formular an. Fehlt die Id, lädt die Seite neu.
        /// </summary>
        /// <returns>Json mit der vergebenen Id.</returns>
        [HttpPost]
        public IActionResult Upload()
        {
            // Die Dropzone meldet sich mit diesem Kopfzeilen-Eintrag. In einem Projekt lässt sich
            // daran ein normaler Formular-Post von einem Dropzone-Upload unterscheiden.
            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];

            // Der im Dialog erfasste Dateiname kommt als eigenes Formularfeld mit, der Inhalt als Datei.
            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 });
        }

        // Nur für die Rückmeldung: macht aus den Ids wieder die Dateinamen.
        private string BeschreibeUpload(IReadOnlyCollection<int> ids)
        {
            if (ids == null || ids.Count == 0)
            {
                return "Keine Dateien übertragen.";
            }

            var namen = ids.Select(id => Dokumente.TryGetValue(id, out var name) ? name : $"Id {id}");
            return $"{ids.Count} Datei(en): {string.Join(", ", namen)}";
        }
    }
}

Darstellung

attach_file

Drag files here or click here to upload.

report_problem

Für diesen Antrag sind noch zwei Beilagen möglich.

MvcConst.MaxFiles ist ein fester Wert mit fester Meldung. Soll die Obergrenze aus dem Modell kommen (z. B. „noch 2 von 5 möglich") oder die Meldung fachlich passen, ist [MaxUploadableFiles] die Wahl:

[Display(Name = "Beilagen")]
[UIHint("IFormFile")]
[MaxUploadableFiles(
    MaxFilesProperty = nameof(VerfuegbarerUpload),
    ErrorMessage = "Für diesen Antrag sind noch zwei Beilagen möglich.")]
public IFormFile Beilage { get; set; }

public int VerfuegbarerUpload { get; set; } = 2;

Optionen

Option Wirkung
MaxFiles Fester Wert; Standard 100. Wird verwendet, wenn keine Eigenschaft angegeben ist.
MaxFilesProperty Name einer Eigenschaft des ViewModels, aus der die Zahl zur Laufzeit gelesen wird. Ist der Wert leer, greift MaxFiles.
ErrorMessage Meldung, sobald das Kontingent ausgeschöpft ist. Ohne Angabe die Standardmeldung aus den Ressourcen.
ResourceType Ressourcen-Typ für die Meldung; ErrorMessage ist dann der Ressourcen-Schlüssel.

Was zu beachten ist

  • Bei gesetztem MaxFilesProperty schreibt das Template die Zahl zusätzlich als verstecktes Feld ins Formular — sie übersteht damit den Round-Trip und steht beim erneuten Rendern wieder zur Verfügung.
  • Ist das Kontingent erreicht, blendet die Ablagefläche den Hinweis ein und nimmt keine weiteren Dateien mehr an; überzählige Dateien entfernt das Skript wieder aus der Liste.
  • [MaxUploadableFiles] ist keine Validierung: Es steuert nur die Ablagefläche im Browser. Wie viele Dokumente tatsächlich ankommen, muss der Endpunkt bzw. die POST-Action selbst prüfen.
  • Gesetzt wird entweder MvcConst.MaxFiles oder [MaxUploadableFiles] — das Attribut gewinnt, weil es nach der Metadaten-Angabe ausgewertet wird.