IFormFile
Zwei Upload-Bereiche in einem Formular
Zwei Felder, zwei Dropzones, zwei Endpunkte – die Zuordnung der Ids läuft über den Eigenschaftsnamen.
@model IFormFileZweiBereicheBeispielViewModel
@* Zwei Upload-Felder in einem Formular: eigene Dropzone, eigener Endpunkt, eigene Id-Liste je
Eigenschaft. Die Zuordnung läuft über den Eigenschaftsnamen. *@
<form asp-area="FileUpload" asp-controller="IFormFileZweiBereicheBeispiel" asp-action="Index" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
@Html.EditorFor(m => m.DokumentIds)
@Html.EditorFor(m => m.Dokument)
<div class="mt-4">
@Html.EditorFor(m => m.ZeugnisIds)
@Html.EditorFor(m => m.Zeugnis)
</div>
@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.Infrastruktur;
using Microsoft.AspNetCore.Http;
namespace FMH.Komponente.WebUI.Areas.FileUpload.Models
{
public class IFormFileZweiBereicheBeispielViewModel
{
[Display(Name = "Dokumente")]
[UIHint("IFormFile")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/IFormFileZweiBereicheBeispiel/Upload")]
public IFormFile Dokument { get; set; }
[UIHint("HiddenList")]
public List<int> DokumentIds { get; set; } = new List<int>();
// Zweiter Bereich mit eigenem Endpunkt: Jede Eigenschaft bringt ihre eigene Dropzone,
// ihre eigene Upload-Url und ihre eigene Id-Liste mit.
[Display(Name = "Zeugnisse")]
[UIHint("IFormFile")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/IFormFileZweiBereicheBeispiel/UploadZeugnis")]
public IFormFile Zeugnis { get; set; }
[UIHint("HiddenList")]
public List<int> ZeugnisIds { 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-Endpunkte, Ablage und Formular-Round-Trip stehen
// vollständig in dieser Datei. Lerninhalt hier: zwei Upload-Felder in einem Formular, jedes mit
// eigener Dropzone, eigener Upload-Adresse und eigener Id-Liste.
[Area("FileUpload")]
public class IFormFileZweiBereicheBeispielController : 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 IFormFileZweiBereicheBeispielViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(IFormFileZweiBereicheBeispielViewModel modell)
{
// Beide Bereiche bringen ihre eigene Id-Liste mit; zugeordnet werden sie über den
// Eigenschaftsnamen (Dokument -> DokumentIds, Zeugnis -> ZeugnisIds).
var meldung = $"Dokumente: {this.BeschreibeUpload(modell.DokumentIds)} — "
+ $"Zeugnisse: {this.BeschreibeUpload(modell.ZeugnisIds)}";
// PRG-Pattern (Post-Redirect-Get): Meldung merken und per Redirect auf die GET-Action.
this.AddAlert($"Formular übertragen – {meldung}", AlertType.Success, autoHide: true);
return this.RedirectToAction(nameof(this.Index));
}
/// <summary>
/// Upload-Endpunkt des ersten Bereichs (Eigenschaft <c>Dokument</c>); seine Adresse steht als
/// <see cref="FMH.Komponente.Infrastruktur.MvcConst.MetaDataUploadUrl" /> am ViewModel.
/// </summary>
/// <returns>Json mit der vergebenen Id.</returns>
[HttpPost]
public IActionResult Upload()
{
return this.SpeichereDatei();
}
/// <summary>
/// Zweiter Endpunkt, für die Eigenschaft <c>Zeugnis</c> — jede Dropzone kann ihre eigene
/// Adresse haben. Fachlich landen die Dateien hier in einer anderen Ablage.
/// </summary>
/// <returns>Json mit der vergebenen Id.</returns>
[HttpPost]
public IActionResult UploadZeugnis()
{
return this.SpeichereDatei();
}
// Beide Endpunkte nehmen die Datei gleich entgegen; unterschiedlich ist in einem Projekt
// nur, wohin sie gespeichert wird. Antwort muss ein Json mit der Eigenschaft id sein — das
// Template legt daraus ein verstecktes Feld <Feldname>Ids im Formular an.
private IActionResult SpeichereDatei()
{
// 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
Mehrere Upload-Felder in einem Formular sind der Normalfall — jede Eigenschaft bringt ihre eigene Ablagefläche, ihre eigene Upload-Adresse und ihre eigene Id-Liste mit:
[UIHint("IFormFile")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/IFormFileZweiBereicheBeispiel/Upload")]
public IFormFile Dokument { get; set; }
[UIHint("HiddenList")]
public List<int> DokumentIds { get; set; } = new List<int>();
[UIHint("IFormFile")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/IFormFileZweiBereicheBeispiel/UploadZeugnis")]
public IFormFile Zeugnis { get; set; }
[UIHint("HiddenList")]
public List<int> ZeugnisIds { get; set; } = new List<int>();
Was zu beachten ist
- Die Trennung läuft ausschliesslich über den Eigenschaftsnamen: Er bestimmt die Id der Ablagefläche, den Namen der Id-Felder (
DokumentIds,ZeugnisIds) und den Namen der globalen Dropzone (window.Dokument,window.Zeugnis). - Beide Bereiche dürfen denselben Endpunkt verwenden; getrennte Endpunkte lohnen sich, wenn die Ablage fachlich unterschiedlich ist (anderer Dokumenttyp, andere Berechtigung).
- Die Ids landen unabhängig voneinander im Formular und kommen im POST als zwei getrennte Listen an.