La chose qui me volait les vendredis
Chaque après‑midi du vendredi, pendant environ un an, j’avais le même petit rituel. Un contrat arrivait sous forme de trois fichiers — le contrat principal sous Word, une annexe tarifaire sous Excel, et la feuille de conditions du partenaire en PDF — et je devais les remettre sous forme d’un seul PDF propre. Rien de difficile. Ouvrir Word, exporter en PDF. Ouvrir Excel, exporter en PDF. Ouvrir une application gratuite de fusion PDF, glisser les trois fichiers, vérifier l’ordre, enregistrer.
Cela prenait peut‑être huit minutes. Multipliez cela par quinze contrats par semaine et vous avez perdu deux heures à bouger la souris. Pire, toutes les quelques semaines, quelqu’un envoyait un classeur avec l’annexe en première page parce que les noms de fichiers étaient triés alphabétiquement dans l’application de fusion.
Si cela vous semble familier, le reste de cet article raconte l’après‑midi où j’ai finalement remplacé le rituel par du code.
Le vrai coût n’est pas le temps — c’est le contrat sur cinquante où les pages sont dans le mauvais ordre et personne ne le remarque jusqu’à ce que le client signe la mauvaise version.
Ce que je voulais réellement
Pas « une chaîne de traitement de documents sophistiquée ». Juste trois choses :
- Donner à une méthode une liste de fichiers (n’importe quel mélange de DOCX, XLSX, PDF) et obtenir un PDF en retour.
- Appliquer la même logique à un dossier et laisser le code déterminer lui‑même la liste des fichiers.
- Extraire une plage de pages du classeur final sans refaire toute la fusion.
C’est tout le travail. Si la bibliothèque ne peut pas faire ces trois points proprement, je ne veux pas le savoir.
Configuration
- .NET 6.0 ou supérieur
- GroupDocs.Merger for .NET 24.10+ (obtenez une licence temporaire pour ne pas embarquer le filigrane d’évaluation)
- Un dossier contenant le mélange de documents que vous assemblez habituellement
dotnet add package GroupDocs.Merger
C’est tout pour les dépendances. Pas de convertisseur externe, pas d’installation Office en mode headless, pas de bibliothèque de manipulation PDF supplémentaire.
Étape 1 — Laisser un dossier être l’entrée
Je commence toujours ici parce que c’est le point d’entrée réaliste. En pratique, quelque chose d’autre (un gestionnaire d’upload, un job d’ingestion d’e‑mail, un dump nocturne depuis la finance) dépose un tas de fichiers dans un répertoire, et mon code doit gérer ce qu’il trouve.
// Récupère chaque fichier supporté dans le dossier de dépôt ; le PDF gagne
// le tie‑break pour la position 0 afin que le merger garde la sortie
// en PDF quel que soit le nom des fichiers.
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}'.");
L’astuce OrderBy est le point intéressant. GroupDocs.Merger choisit son format de sortie à partir du premier fichier ouvert — si je lui donne un DOCX comme document principal, je reçois un DOCX en sortie. Comme mon pipeline veut toujours un PDF, je m’assure que tout PDF présent dans le dossier obtient la position 0.
Deux points à mentionner :
ToLowerInvariant()parce qu’un jour un partenaire pourra vous envoyerREPORT.PDFet votre filtre sensible à la casse le rejettera silencieusement.- Le
ThenBy(f)n’est là que pour rendre la sortie déterministe. Sans cela, deux exécutions sur le même dossier peuvent différer selon l’humeur du système de fichiers.
Étape 2 — La fusion proprement dite
Une fois que j’ai une liste ordonnée de chemins, la fusion est plus courte que la description de la fusion.
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)}");
Quelques notes tirées d’une utilisation intensive :
- Le
usingcompte.Mergergarde des poignées de fichiers sur les sources ; oubliez de le disposer et votre worker de dossier de dépôt finira par ne plus pouvoir supprimer ses propres entrées. JoinOptionsest vide ici parce que les valeurs par défaut sont ce que je veux 95 % du temps. Quand vous avez besoin de le personnaliser, c’est là que résident les plages de pages, la rotation et les positions d’insertion.- Quand Excel entre dans le classeur, la mise en page feuille‑à‑page est décidée par la zone d’impression du classeur source. Si votre XLSX s’étale sur 38 pages alors que vous en vouliez trois, la correction se fait dans la feuille de calcul, pas dans
JoinOptions.
Une vérification de bon sens que j’ajoute toujours juste après l’enregistrement :
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
Deux secondes de code qui ont attrapé plus de bugs « annexe silencieusement rejetée » que n’importe quel test que j’ai écrit.
Étape 3 — Extraire une tranche plus tard
La demande qui revient à chaque fois : « Pouvez‑vous simplement m’envoyer la page de garde ? » ou « Le client ne veut que les signatures. » Reconstituer tout le classeur pour ne remettre que deux pages est absurde — l’extraction le fait directement.
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 est un int[] de numéros de pages (commençant à 1) que vous souhaitez conserver. Tout le reste est éliminé. C’est rapide parce que le résultat est déjà un PDF — pas de aller‑retour de conversion.
Avant vs. après, honnêtement
| Ce que je faisais auparavant | Avec Merger.Join |
|
|---|---|---|
| Temps par contrat | 5 à 10 minutes de clics | moins de 30 secondes de bout en bout |
| Échec typique | Pages dans le mauvais ordre, personne ne remarque | Quel que soit l’ordre indiqué par la liste de fichiers, de façon répétable |
| Mise à l’échelle à 100/jour | Pas possible — vous embauchez une personne | Un seul worker, ennuyé la plupart du temps |
| Code que vous maintenez | Une page Confluence intitulée “Binder Process v4” | Une classe, ~70 lignes |
| Résultat | Trois PDFs et une prière | Un seul classeur, avec le nombre de pages que vous pouvez logger |
La ligne qui m’importe le plus est celle du « échec ». La fusion manuelle échoue silencieusement ; le code qui journalise le nombre de pages échoue bruyamment.
Une histoire réelle d’une petite équipe legal‑tech
Une startup de deux personnes avec laquelle j’ai travaillé avait une assistante juridique dont la matinée commençait par l’assemblage de contrats. Accord Word, tarification Excel, addendum PDF, assemblés dans une appli, puis uploadés sur DocuSign. Environ huit minutes par paquet, ce qui à 30 paquets par jour constituait pratiquement toute sa matinée.
Ils ont intégré la méthode de scan de dossier dans le service backend qui surveillait déjà leur boîte mail d’entrée. Vingt secondes par paquet, plus une ligne de log avec le nombre de pages. L’assistante est passée à la révision des contrats au lieu de les assembler. Plus aucun classeur mal ordonné n’a été envoyé — non pas parce que la bibliothèque est magique, mais parce que la liste de fichiers est explicite dans le code et vous pouvez la comparer.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
C’est toute l’intégration. Tout ce qui était en amont (l’écouteur d’e‑mail, le chemin de stockage) était déjà en place.
Ce dont je n’ai pas eu besoin aujourd’hui mais dont j’aurai besoin demain
La même bibliothèque propose un tas de fonctionnalités que je n’ai pas abordées parce que l’article deviendrait trop long. Dans l’ordre approximatif où je les ai découverts :
- Filigranes sur la sortie pour les mentions « DRAFT » sur les copies pré‑signature.
- Rotation de pages pour les scans qui arrivent de travers.
- Ordonnancement personnalisé des pages lorsque l’ordre source n’est pas l’ordre de livraison.
- Chiffrement PDF pour tout ce qui part vers un contre‑partie externe.
Tout cela se trouve derrière la même API Merger. La documentation contient la liste complète — je voulais simplement souligner que « merge » est le point d’entrée économique et que le reste est disponible quand il faut.
Ce que je dirais à mon moi du passé
Si vous êtes sur le point d’écrire votre propre étape DOCX‑to‑PDF parce que « c’est juste une méthode », arrêtez‑vous. La conversion est la partie qui se corrode en silence — nouvelles fonctionnalités Office, gestion d’images scannées, polices embarquées, etc. Laissez autre chose gérer cette surface, et passez votre vendredi après‑midi sur quelque chose qui n’est pas le tri de noms de fichiers.
Où aller ensuite :
- Licence temporaire — requise pour une sortie sans filigrane
- Options avancées de fusion — JoinOptions, options d’enregistrement, compression
- Formats supportés — bien au‑delà des trois présentés ici
- Projets d’exemple sur GitHub — y compris celui‑ci