Das, was mir jeden Freitag die Zeit raubte
Jeden Freitagnachmittag, etwa ein Jahr lang, hatte ich dasselbe kleine Ritual. Ein Vertrag kam als drei Dateien – das Hauptabkommen in Word, ein Preisanhang in Excel und ein Bedingungen‑Blatt des Partners als PDF – und ich musste sie zu einem sauberen PDF zusammenführen. Nichts Schwieriges. Word öffnen, als PDF exportieren. Excel öffnen, als PDF exportieren. Eine kostenlose PDF‑Merge‑App öffnen, die drei Dateien hineinziehen, die Reihenfolge prüfen, speichern.
Es dauerte etwa acht Minuten. Multipliziert man das mit fünfzehn Verträgen pro Woche, verliert man zwei Stunden durch Mausbewegungen. Schlimmer noch, alle paar Wochen verschickte jemand einen Ordner, bei dem der Anhang auf Seite eins stand, weil die Dateinamen in der Merge‑App alphabetisch sortiert wurden.
Wenn das vertraut klingt, ist der Rest dieses Beitrags der Nachmittag, an dem ich das Ritual endlich durch Code ersetzt habe.
Die eigentlichen Kosten sind nicht die Zeit – es ist der eine Vertrag von fünfzig, bei dem die Seiten in falscher Reihenfolge landen und niemand es bemerkt, bis der Kunde die falsche Version unterschreibt.
Was ich eigentlich wollte
Nicht „eine ausgefallene Dokumenten‑Pipeline“. Nur drei Dinge:
- Einer Methode eine Liste von Dateien (beliebige Mischung aus DOCX, XLSX, PDF) übergeben und ein PDF zurückbekommen.
- Die gleiche Logik auf einen Ordner anwenden und sie die Dateiliste selbst ermitteln lassen.
- Einen Seitenbereich aus dem fertigen Ordner extrahieren, ohne das gesamte Zusammenführen erneut durchzuführen.
Das ist die gesamte Aufgabe. Wenn die Bibliothek diese drei Dinge nicht sauber erledigen kann, will ich nichts davon wissen.
Einrichtung
- .NET 6.0 oder höher
- GroupDocs.Merger für .NET 24.10+ (grab a temporary license damit Sie das Evaluations‑Wasserzeichen nicht mitliefern)
- Ein Ordner mit beliebiger Mischung von Dokumenten, die Sie normalerweise manuell zusammenführen würden
dotnet add package GroupDocs.Merger
Das ist alles für die Abhängigkeiten. Kein externer Konverter, keine headless Office‑Installation, keine zusätzliche PDF‑Manipulationsbibliothek.
Schritt 1 — Einen Ordner als Eingabe verwenden
Ich beginne immer hier, weil es der realistische Einstiegspunkt ist. In der Praxis legt etwas anderes (ein Upload‑Handler, ein E‑Mail‑Import‑Job, ein nächtlicher Dump aus der Buchhaltung) eine Menge Dateien in ein Verzeichnis, und mein Code muss mit dem umgehen, was er findet.
// Pick up every supported file in the drop folder; the PDF wins
// the tie-break for position 0 so the merger keeps the output
// as a PDF regardless of how files are named.
string[] extensions = { ".pdf", ".docx", ".xlsx" };
var files = Directory.EnumerateFiles(folderPath)
.Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
.OrderBy(f => Path.GetExtension(f).ToLowerInvariant() == ".pdf" ? 0 : 1)
.ThenBy(f => f)
.ToArray();
if (files.Length == 0)
throw new InvalidOperationException(
$"No supported documents found in '{folderPath}'.");
Der OrderBy‑Trick ist das Interessante. GroupDocs.Merger wählt sein Ausgabeformat anhand der zuerst geöffneten Datei – wenn ich ihm ein DOCX als Hauptdokument übergebe, erhalte ich ein DOCX zurück. Da meine Pipeline immer ein PDF ausgeben soll, stelle ich sicher, dass jede vorhandene PDF im Ordner Position 0 erhält.
Zwei Dinge, die erwähnenswert sind:
ToLowerInvariant(), weil ein Partner eines TagesREPORT.PDFsendet und Ihr ausschließlich kleingeschriebener Filter diese stillschweigend verwirft.- Das
ThenBy(f)ist nur da, um die Ausgabe deterministisch zu machen. Ohne es können zwei Durchläufe im selben Ordner je nach Dateisystem‑Stimmung unterschiedliche Ergebnisse liefern.
Schritt 2 — Das Zusammenführen selbst
Sobald ich eine sortierte Pfadliste habe, ist das Zusammenführen kürzer als die Beschreibung des Zusammenführens.
Console.WriteLine($"Primary source: {sourcePaths[0]}");
using var merger = new Merger(sourcePaths[0]);
var joinOptions = new JoinOptions();
for (int i = 1; i < sourcePaths.Length; i++)
{
Console.WriteLine($"Joining: {sourcePaths[i]}");
merger.Join(sourcePaths[i], joinOptions);
}
merger.Save(outputPath);
Console.WriteLine($"Unified PDF binder saved to: {Path.GetFullPath(outputPath)}");
Ein paar Anmerkungen aus der Praxis:
- Das
usingist wichtig.Mergerhält Dateihandles zu den Quellen; wenn Sie es nicht entsorgen, wird Ihr Drop‑Folder‑Worker irgendwann seine eigenen Eingaben nicht mehr löschen können. JoinOptionsist hier leer, weil die Vorgaben 95 % der Zeit das sind, was ich will. Wenn Sie es benötigen, finden Sie dort Seitenbereiche, Drehungen und Einfügepositionen.- Wenn Excel in den Ordner aufgenommen wird, wird das Layout von Blatt zu Seite durch den Druckbereich der Quell‑Arbeitsmappe bestimmt. Wenn Ihr XLSX auf 38 Seiten endet und Sie drei wollten, liegt die Lösung im Tabellenblatt, nicht in
JoinOptions.
Eine Plausibilitätsprüfung, die ich immer direkt nach dem Speichern einbaue:
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
Zwei Sekunden Code, die mehr „stillschweigend verworfene Anhänge“-Fehler gefangen haben als jeder Test, den ich geschrieben habe.
Schritt 3 — Einen Ausschnitt später extrahieren
Die Anschlussfrage, die ich jedes Mal erhalte: „Können Sie mir einfach die Titelseite schicken?“ oder „Der Kunde will nur die Unterschriften.“ Das gesamte Dokument neu zu bauen, um nur zwei Seiten zu übergeben, ist unsinnig – Extract erledigt das direkt.
using var merger = new Merger(binderPath);
merger.ExtractPages(new ExtractOptions(pages));
merger.Save(outputPath);
Console.WriteLine($"Extracted pages [{string.Join(",", pages)}] to " +
Path.GetFullPath(outputPath));
pages ist ein int[] mit 1‑basierten Seitenzahlen, die Sie behalten möchten. Alles andere wird verworfen. Es ist schnell, weil das Ergebnis bereits ein PDF ist – kein Konvertierungs‑Round‑Trip.
Vorher vs. nachher, ehrlich
| Was ich früher gemacht habe | Mit Merger.Join |
|
|---|---|---|
| Zeit pro Vertrag | 5–10 Minuten Klicken | unter 30 Sekunden von Anfang bis Ende |
| Typischer Fehler | Seiten in falscher Reihenfolge, niemand bemerkt es | Welche Reihenfolge die Dateiliste auch angibt, wiederholbar |
| Skalierung auf 100/Tag | Nicht möglich – Sie stellen eine Person ein | Ein Arbeiter, die meiste Zeit gelangweilt |
| Code, den Sie pflegen | Eine Confluence‑Seite mit dem Titel „Binder Process v4“ | Eine Klasse, ~70 Zeilen |
| Ausgabe | Drei PDFs und ein Gebet | Ein Ordner, mit Seitenzahl, die Sie protokollieren können |
Die Zeile, die mir am wichtigsten ist, ist die „Fehler“-Zeile. Manuelles Zusammenführen schlägt stillschweigend fehl; Code, der die Seitenzahl protokolliert, schlägt lautstark fehl.
Eine wahre Geschichte von einem kleinen Legal-Tech-Team
Ein zweiköpfiges Startup, mit dem ich zusammenarbeitete, hatte eine Paralegal, deren Morgen mit der Zusammenstellung von Verträgen begann. Word‑Vereinbarung, Excel‑Preisgestaltung, PDF‑Anhang, in einer App zusammengefügt, zu DocuSign hochgeladen. Etwa acht Minuten pro Paket, bei 30 Paketen pro Tag war das praktisch ihr ganzer Morgen.
Sie integrierten die Ordner‑Scan‑Methode in den Backend‑Dienst, der bereits ihre Eingangs‑E‑Mails beobachtete. Zwanzig Sekunden pro Paket, plus eine Log‑Zeile mit der Seitenzahl. Die Paralegal wechselte zum Vertrags‑Review statt zum Zusammenstellen. Niemand verschickte wieder einen falsch sortierten Ordner – nicht weil die Bibliothek magisch ist, sondern weil die Dateiliste im Code explizit ist und man sie vergleichen kann.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
Das ist die gesamte Integration. Alles Vorherige (der E‑Mail‑Listener, der Speicherpfad) war bereits vorhanden.
Dinge, die ich heute nicht brauchte, aber morgen brauchen werde
Die gleiche Bibliothek kann eine Menge Dinge, die ich hier nicht behandelt habe, weil der Artikel sonst zu lang wäre. In etwa der Reihenfolge, in der ich sie verwendet habe:
- Wasserzeichen auf der Ausgabe für „ENTWURF“-Stempel auf Vor‑Unterschrifts‑Kopien.
- Seitendrehung für Scans, die seitlich kommen.
- Benutzerdefinierte Seitenreihenfolge wenn die Quellreihenfolge nicht der Lieferreihenfolge entspricht.
- PDF‑Verschlüsselung für alles, das an einen externen Gegenüber geht.
All das steckt hinter derselben Merger‑API. Die docs enthalten die vollständige Liste – ich wollte nur darauf hinweisen, dass „merge“ der günstige Einstieg ist und der Rest verfügbar ist, wenn Sie ihn benötigen.
Was ich meinem früheren Ich sagen würde
Wenn Sie gerade dabei sind, Ihren eigenen DOCX‑zu‑PDF‑Schritt zu schreiben, weil „es nur eine Methode ist“, stoppen Sie. Die Konvertierung ist der Teil, der stillschweigend verrottet – neue Office‑Funktionen, Umgang mit gescannten Bildern, eingebettete Schriften und so weiter. Lassen Sie etwas anderes diese Oberfläche übernehmen und verbringen Sie Ihren Freitagnachmittag mit etwas, das nicht das Sortieren von Dateinamen ist.
Wo Sie als Nächstes hingehen können:
- Temporary license — erforderlich für ausgabefreies Wasserzeichen
- Advanced merging options — JoinOptions, Speicheroptionen, Kompression
- Supported formats — weit über die hier gezeigten drei hinaus
- Sample projects on GitHub — einschließlich dieses