Het ding dat mijn vrijdagen opslokte
Elke vrijdagmiddag, ongeveer een jaar lang, had ik hetzelfde kleine ritueel. Een contract kwam binnen als drie bestanden — de hoofdovereenkomst in Word, een prijsbijlage in Excel, en een voorwaardenblad van een partner als PDF — en ik moest ze omzetten naar één nette PDF. Niets moeilijks. Open Word, exporteer naar PDF. Open Excel, exporteer naar PDF. Open een gratis PDF‑samenvoegapp, sleep de drie bestanden erin, controleer de volgorde, sla op.
Het kostte ongeveer acht minuten. Vermenigvuldig dat met vijftien contracten per week en je verliest twee uur aan muisklikken. Erger nog, elke paar weken verscheen er een binder met de bijlage op pagina één omdat de bestandsnamen alfabetisch werden gesorteerd in de samenvoegapp.
Als dit je bekend voorkomt, is de rest van dit bericht de middag waarop ik het ritueel eindelijk verving door code.
De echte kost is niet de tijd — het is dat ene contract in de vijftig waarbij de pagina’s in de verkeerde volgorde terechtkomen en niemand het merkt tot de klant de verkeerde versie ondertekent.
Wat ik eigenlijk wilde
Niet “een fancy document‑pipeline.” Gewoon drie dingen:
- Geef een methode een lijst van bestanden (een willekeurige mix van DOCX, XLSX, PDF) en krijg één PDF terug.
- Laat dezelfde logica op een map wijzen en laat het zelf de bestandenlijst bepalen.
- Haal een paginabereik uit de voltooide binder zonder de hele samenvoeging opnieuw te doen.
Dat is de hele klus. Als de bibliotheek die drie dingen niet netjes kan, wil ik er niets van weten.
Setup
- .NET 6.0 of later
- GroupDocs.Merger for .NET 24.10+ (grab a temporary license zodat je de eval‑watermark niet meegeeft)
- Een map met de willekeurige mix documenten die je normaal handmatig zou samenvoegen
dotnet add package GroupDocs.Merger
Dat is alles wat je nodig hebt. Geen externe converter, geen headless Office‑installatie, geen PDF‑manipulatie‑bibliotheek er bovenop.
Stap 1 — Laat een map de invoer zijn
Ik begin altijd hier omdat het de realistische instap is. In de praktijk landt iets anders (een upload‑handler, een e‑mail‑inname‑job, een nachtelijke dump van de financiële afdeling) een hoop bestanden in een directory, en mijn code moet met wat er ook gevonden wordt omgaan.
// 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}'.");
De OrderBy‑truc is het interessante deel. GroupDocs.Merger kiest zijn uitvoerformaat op basis van het eerste bestand dat je opent — als ik een DOCX als primair document geef, krijg ik een DOCX terug. Omdat mijn pipeline altijd een PDF wil, zorg ik ervoor dat elke bestaande PDF in de map positie 0 krijgt.
Twee dingen die het vermelden waard zijn:
ToLowerInvariant()omdat een partner op een dagREPORT.PDFkan sturen en je alleen‑lowercase filter het stilletjes zou laten vallen.- De
ThenBy(f)staat er alleen om de uitvoer deterministisch te maken. Zonder die stap kunnen twee runs op dezelfde map verschillen door de stemming van het bestandssysteem.
Stap 2 — De samenvoeging zelf
Zodra ik een geordende lijst van paden heb, is de samenvoeging korter dan de beschrijving ervan.
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)}");
Een paar aantekeningen uit het gebruik in de praktijk:
- Het
usingis belangrijk.Mergerhoudt bestands‑handles op de bronnen; vergeet je het te disposen dan zal je drop‑folder‑worker uiteindelijk niet meer in staat zijn zijn eigen invoer te verwijderen. JoinOptionsis hier leeg omdat de standaardinstellingen 95 % van de tijd precies zijn wat ik wil. Wanneer je het wel nodig hebt, vind je daar paginabereiken, rotatie en invoegposities.- Wanneer Excel in de binder terechtkomt, wordt de blad‑naar‑pagina‑lay‑out bepaald door het afdrukgebied van de bron‑werkmap. Als je XLSX op 38 pagina’s uitkomt en je wilt er drie, dan moet je dat in de spreadsheet aanpassen, niet in
JoinOptions.
Een sanity‑check die ik altijd direct na het opslaan toevoeg:
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
Twee seconden code die meer “stilletjes gedropte bijlage” bugs heeft gepakt dan welke test ik ook schreef.
Stap 3 — Later een slice extraheren
De vervolg‑vraag die ik elke keer krijg: “Kun je me gewoon de voorpagina sturen?” of “De klant wil alleen de handtekeningen.” Het hele binder opnieuw opbouwen om twee pagina’s te leveren is onnodig — ExtractPages doet het direct.
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 is een int[] met 1‑gebaseerde paginanummers die je wilt behouden. Alles andere wordt weggelaten. Het is snel omdat het resultaat al een PDF is — geen conversie‑ronde‑trip.
Voor en na, eerlijk gezegd
| Wat ik vroeger deed | Met Merger.Join |
|
|---|---|---|
| Tijd per contract | 5–10 minuten klikken | onder 30 seconden end‑to‑end |
| Typische fout | Pagina’s in de verkeerde volgorde, niemand merkt het | Welke volgorde de bestandenlijst ook aangeeft, consistent |
| Opschalen naar 100/dag | Niet mogelijk — je huurt een persoon | Eén worker, meestal verveeld |
| Code die je onderhoudt | Een Confluence‑pagina getiteld “Binder Process v4” | Eén klasse, ~70 regels |
| Uitvoer | Drie PDF’s en een gebed | Eén binder, met paginatelling die je kunt loggen |
De rij die mij het meest interesseert is de “fout”‑rij. Handmatig samenvoegen faalt stilletjes; code die een paginatelling logt faalt luid.
Een echt verhaal van een klein legal‑tech team
Een startup van twee personen waar ik mee werkte had een paralegal waarvan de ochtend begon met contract‑assemblage. Word‑overeenkomst, Excel‑prijsstelling, PDF‑addendum, in een app gestikt, geüpload naar DocuSign. Ongeveer acht minuten per pakket, wat bij 30 pakketten per dag praktisch haar hele ochtend was.
Ze plaatsten de map‑scan‑methode in de backend‑service die al naar hun inkomende e‑mail keek. Twintig seconden per pakket, plus een log‑regel met de paginatelling. De paralegal ging over op contract‑review in plaats van assemblage. Niemand leverde nog een verkeerd geordende binder — niet omdat de bibliotheek magie is, maar omdat de bestandenlijst expliciet in de code staat en je die kunt diff’en.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
Dat is de volledige integratie. Alles stroomopwaarts (de e‑mail‑listener, het opslagpad) was al aanwezig.
Dingen die ik vandaag niet nodig had maar morgen wel
Dezelfde bibliotheek kan een hoop meer dan ik hier behandeld heb, omdat het artikel anders te lang zou worden. In ruwweg de volgorde waarin ik ze heb gebruikt:
- Watermarks on the output voor “DRAFT” stempels op pre‑handtekening kopieën.
- Page rotation voor scans die scheef binnenkomen.
- Custom page ordering wanneer de bronvolgorde niet de leveringsvolgorde is.
- PDF encryption voor alles wat naar een externe tegenpartij gaat.
Al dat zit achter dezelfde Merger‑API. De docs bevatten de volledige lijst — ik wilde alleen aangeven dat “merge” de goedkope starter is en de rest beschikbaar is wanneer je het nodig hebt.
Wat ik mijn vroegere ik zou vertellen
Als je op het punt staat je eigen DOCX‑naar‑PDF stap te schrijven omdat “het maar één methode is”, stop dan. De conversie is het onderdeel dat stilletjes roest — nieuwe Office‑features, gescande‑afbeeldingen, ingebedde lettertypen, en zo. Laat iets anders dat oppervlak beheren en besteed je vrijdagmiddag aan iets dat niet alleen bestandsnaam‑sortering is.
Waar je daarna heen kunt gaan:
- Temporary license — vereist voor watermerk‑vrije uitvoer
- Advanced merging options — JoinOptions, save‑options, compressie
- Supported formats — ver voorbij de drie die ik hier liet zien
- Sample projects on GitHub — inclusief dit project