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><Feldname>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
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
MaxFilesPropertyschreibt 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.MaxFilesoder[MaxUploadableFiles]— das Attribut gewinnt, weil es nach der Metadaten-Angabe ausgewertet wird.