DokumentListe
Dokumente bearbeiten
Umbenennen je Zeile und Mehrfachlöschen über eine Auswahlspalte. Beides wird nur markiert und erst in der POST-Action ausgeführt.
@model DokumentListeBearbeitenBeispielViewModel
@* Umbenennen und Mehrfachlöschen wirken nur auf die Tabelle: Im Browser wird lediglich markiert,
gesammelt in DokumenteUmbenennen bzw. DokumentLoeschenIds. Ausgeführt wird beides erst in der
POST-Action. *@
<form asp-area="FileUpload" asp-controller="DokumentListeBearbeitenBeispiel" asp-action="Index" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
@Html.EditorFor(m => m.PosterBelege)
@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 DokumentListeBearbeitenBeispielViewModel
{
// MetaDataRename ergänzt je Zeile eine Schaltfläche zum Umbenennen, MetaDataBulkDelete eine
// Auswahlspalte samt Sammel-Löschen. Beide Angaben wirken nur auf die Tabelle der bereits
// vorhandenen Dokumente, nicht auf die Ablagefläche.
[UIHint("DokumentListe")]
[Display(Name = "Poster hochladen")]
[AdditionalMetadata(MvcConst.MetaDataRename, "true")]
[AdditionalMetadata(MvcConst.MetaDataBulkDelete, "true")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListeBearbeitenBeispiel/Upload")]
[AdditionalMetadata(MvcConst.MaxFileSizeInMb, "1")]
[AdditionalMetadata(MvcConst.MaxFiles, "10")]
[AdditionalMetadata(MvcConst.MetaDataDownloadActionName, "Download")]
[AdditionalMetadata(MvcConst.MetaDataDownloadControllerName, "DokumentListeBearbeitenBeispiel")]
public DokumentListeViewModel PosterBelege { 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
{
// Zeigt Umbenennen (MetaDataRename) und Mehrfachlöschen (MetaDataBulkDelete). Beide Vorgänge
// laufen im Browser nur als Markierung: Das Template sammelt sie in DokumenteUmbenennen bzw.
// DokumentLoeschenIds, ausgeführt werden sie erst hier in der POST-Action.
[Area("FileUpload")]
public class DokumentListeBearbeitenBeispielController : Controller
{
private static readonly ConcurrentDictionary<int, string> Dokumente =
new ConcurrentDictionary<int, string>(
new Dictionary<int, string>
{
[5] = "Kurs.pdf",
[6] = "Kurs2.pdf",
[7] = "Kurs3.pdf",
[8] = "Kurs4.pdf",
[9] = "Kurs5.pdf",
[10] = "Kurs6.pdf",
});
private static int letzteId = 100;
// Auch die Doku-Seite zeigt die gefüllte Liste — deshalb öffentlich.
public static DokumentListeBearbeitenBeispielViewModel ErstelleModell()
{
var modell = new DokumentListeBearbeitenBeispielViewModel();
modell.PosterBelege.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(DokumentListeBearbeitenBeispielViewModel modell)
{
var liste = modell.PosterBelege;
var teile = new List<string>();
if (liste.DokumentIds != null && liste.DokumentIds.Length > 0)
{
teile.Add($"neu: {string.Join(", ", liste.DokumentIds.Select(NameZuId))}");
}
// Umbenennen: Das Template schreibt je Änderung DokumentEintragId und Name in die
// Sammlung DokumenteUmbenennen. In einem Projekt würde die neue Bezeichnung hier
// gespeichert; die Demo meldet sie nur zurück, damit die Ausgangslage erhalten bleibt.
if (liste.DokumenteUmbenennen != null && liste.DokumenteUmbenennen.Length > 0)
{
var umbenannt = liste.DokumenteUmbenennen.Select(
eintrag => $"{NameZuId(eintrag.DokumentEintragId)} → {eintrag.Name}");
teile.Add($"umbenannt: {string.Join(", ", umbenannt)}");
}
// Löschen: Ebenfalls erst hier — im Browser wurde die Zeile nur ausgeblendet und die Id
// gesammelt. Auch hier bleibt die Demo-Ablage bewusst unverändert, damit das Beispiel
// beliebig oft durchgespielt werden kann.
if (liste.DokumentLoeschenIds != null && liste.DokumentLoeschenIds.Length > 0)
{
teile.Add($"gelöscht: {string.Join(", ", liste.DokumentLoeschenIds.Select(NameZuId))}");
}
this.AddAlert(
teile.Count == 0
? "Formular übertragen – keine Änderungen."
: $"Formular übertragen – {string.Join(" | ", teile)}",
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 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 name) == false)
{
return this.NotFound();
}
var inhalt = Encoding.UTF8.GetBytes($"Platzhalter-Inhalt für «{name}» (Id {id}).");
return this.File(inhalt, "text/plain", name);
}
private static string NameZuId(int id)
{
return Dokumente.TryGetValue(id, out var name) ? name : $"Id {id}";
}
}
}
Darstellung
Zwei Angaben erweitern die Tabelle der bereits zugeordneten Dokumente: MetaDataRename ergänzt je
Zeile eine Schaltfläche zum Umbenennen, MetaDataBulkDelete eine Auswahlspalte samt Sammel-Löschen.
[UIHint("DokumentListe")]
[Display(Name = "Poster hochladen")]
[AdditionalMetadata(MvcConst.MetaDataRename, "true")]
[AdditionalMetadata(MvcConst.MetaDataBulkDelete, "true")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListeBearbeitenBeispiel/Upload")]
public DokumentListeViewModel PosterBelege { get; set; } = new DokumentListeViewModel();
Was beim Absenden ankommt
[HttpPost]
public IActionResult Index(DokumentListeBearbeitenBeispielViewModel modell)
{
var neu = modell.PosterBelege.DokumentIds; // frisch hochgeladen
var umbenannt = modell.PosterBelege.DokumenteUmbenennen; // DokumentEintragId + neuer Name
var geloescht = modell.PosterBelege.DokumentLoeschenIds; // zum Löschen markiert
...
}
- Umbenennen schreibt je Änderung ein Paar versteckter Felder ins Formular:
<Feldname>.DokumenteUmbenennen[n].DokumentEintragIdund…[n].Name. Gebunden wird das anDokumenteUmbenennen(DokumentEintrag[]). - Löschen – einzeln wie auch als Mehrfachauswahl – hängt die Ids an
<Feldname>.DokumentLoeschenIdsund blendet die Zeile aus.
Was zu beachten ist
- Im Browser wird nur markiert. Das tatsächliche Umbenennen und Löschen gehört in die POST-Action — passiert dort nichts, sieht der Benutzer die Änderung nach dem nächsten Laden nicht.
- Das Mehrfachlöschen fragt vor dem Ausblenden über einen Bestätigungsdialog nach.
- Beim Löschen zählt das Template das versteckte Feld
<Feldname>ExistingDocumentsherunter. Ist die Obergrenze dadurch wieder unterschritten, nimmt die Ablagefläche sofort wieder Dateien an. - Die Angaben wirken nur auf die Tabelle. Ist
Dokumenteleer, wird gar keine Tabelle gerendert und beide Optionen bleiben ohne sichtbare Wirkung.