đź’ˇ Full working example available on GitHub: one-line-document-merge-and-split-dotnet

Where Back-Office Document Jobs Actually Live

A finance back office closes the month by stapling a cover page onto last month’s statement PDF. A contracts team appends a signed amendment to the original agreement before it goes to archive. A sales-ops assistant pulls two slides out of a sixty-slide deck because one stakeholder only ever needed the pricing page. A records clerk drops a page that should never have shipped, right before the file leaves the building.

None of these are merge projects. Each is one file and one operation, done the same way every time a job runs, usually by someone who has no interest in learning a general-purpose document API just to get there.

GroupDocs.Merger.LowCode is a .NET package that turns one such job, a join, a split or a page removal, into a single constructor call, standing apart from the general-purpose Merger class those jobs never need. Each of its seven products takes its source and its options in the constructor, in C#, and runs only once Save is called, the same shape whether the job is a PDF, a Word document or a PowerPoint deck.

Why Generic Fixes Don’t Hold Up Here

Teams facing one of these jobs usually try one of three things before reaching for a focused package:

  • Doing it by hand in a desktop editor: fine for one document, impossible to schedule, and nobody signs off on a manual step running at 2 a.m.
  • Writing a one-off script against raw file APIs: works until the next input format shows up, and the script grows into exactly the general-purpose client it was meant to avoid.
  • Reaching for the full Merger class for a single call: correct, but it adds a wider API surface than one operation ever needed.

GroupDocs.Merger.LowCode exists for exactly that last gap: one call per operation, nothing else to learn first.


The Solution: GroupDocs.Merger.LowCode for Back-Office Pipelines

GroupDocs.Merger.LowCode packages seven single-purpose products behind that one constructor-then-Save shape. JoinPdf and JoinDocx append a document to another; JoinPptx does the same for decks; SplitPdf, SplitDocx and SplitPptx pull chosen pages or slides into files of their own; RemovePagesPdf drops pages without touching the source. Every one of the seven accepts its source as a path or a stream and checks that source, plus every file it is given, against its own format before writing anything, so a pipeline never has to guess why an output came out wrong.

To follow the implementation below, you will need:

  • .NET 6.0 or later (this sample’s project targets net8.0)
  • The separate GroupDocs.Merger.LowCode NuGet package
  • A temporary license, if you want to see output without the evaluation limits described further down
dotnet add package GroupDocs.Merger.LowCode

The package lives separately on NuGet from the main GroupDocs.Merger library.


Implementing the Reporting-Pack Job Step by Step

Here is the shape every one of the seven products follows, walked through with the join that appends a cover page to a monthly PDF.

Step 1: Point the Product at Its Source

JoinPdf’s constructor takes the source document, every file to append, and a JoinOptions instance; nothing runs yet.

var join = new JoinPdf("report.pdf", new[] { "appendix.pdf" }, new JoinOptions());

Step 2: Decide Where the Result Goes

Save(outputPath) is where the job actually commits. A relative path resolves against the process’s current working directory, and Save creates the output folder if it does not already exist, so a pipeline writing into a new month’s folder every run does not need a separate step to provision it.

string outputPath = "output/monthly/report-with-appendix.pdf";

Why this matters for an unattended job:

  • A path you control: build the output path from configuration or a job parameter, never straight from unvalidated input; Save throws ArgumentNullException on a null or empty path.
  • The folder it creates: a scheduled job that writes to a new destination every run does not carry a separate “ensure directory exists” step of its own.

Step 3: Run It and Capture What Came Back

Calling Save runs the join immediately; there is no separate build-then-execute step.

join.Save(outputPath);
var sizeInBytes = new FileInfo(outputPath).Length;

In our own run against the sample’s files, that call joined a 2-page report.pdf to a 2-page appendix.pdf into a 4-page, 257987-byte report-with-appendix.pdf.

Step 4: Feed the Result Back Into the Pipeline

The file size read right after Save is usually all a scheduler needs before marking the job complete, or before comparing it against a minimum size to decide whether the output is trustworthy enough to move on.

if (sizeInBytes == 0)
{
    throw new InvalidOperationException("Join produced an empty file.");
}

Does GroupDocs.Merger.LowCode Accept a Stream Instead of a File Path?

Yes, on the input side. Each of the seven products accepts its source as a stream in the same constructor position where a file path would go, useful when a job’s input comes from blob storage or a queue instead of local disk. Save still writes its result to an output path string either way; streaming the output itself stays in the full Merger class.


Real Workflow: Rejecting a File Before It Reaches Archive

A contracts team appending amendments to signed agreements cannot risk accepting whatever file lands in the inbox; a .docx amendment misfiled next to a PDF join would otherwise produce a broken archive entry. JoinPdf checks the source and every file being appended against the PDF format before writing anything, and raises FileTypeNotSupportedException the moment one of them does not match.

try
{
    new JoinPdf("report.pdf", new[] { "contract.docx" }, new JoinOptions())
        .Save("output/mixed.pdf");
}
catch (FileTypeNotSupportedException exception)
{
    Console.WriteLine(exception.Message);
}

In the sample run, where the paths are built from its resources folder, the message read: Input format does not match the expected format 'PDF'; the joined file 'resources\contract.docx' is 'Microsoft Word Open XML Document (.docx)'.

The job above never writes output/mixed.pdf when the catch fires, so a contracts pipeline can route that message straight into whatever alert or ticket queue it already uses, instead of discovering a bad archive entry days later.


Other Back-Office Scenarios Where This Pattern Fits

Once the join above is working, the same constructor-then-Save shape covers several jobs a back office runs on a schedule:

  • Trimming a page before a document leaves the building: RemovePagesPdf drops the chosen pages and writes a new file without touching the source, so the original stays available for audit after the trimmed copy ships. In our own run, removing page 2 from report.pdf produced a 1-page, 86647-byte report-without-page-2.pdf, and report.pdf itself stayed unchanged.
  • Splitting a deck for one stakeholder: SplitPptx pulls the requested slides into files of their own, named by a {0} token in the output pattern or, without one, by an appended part number in the same folder, the same pattern SplitPdf uses to split PDF pages.
  • Merging Word documents instead of PDFs: JoinDocx follows the exact constructor- then-Save shape as JoinPdf, placing the appended document after the last page of the source.

Without a license, every one of these runs in evaluation mode. In our own unlicensed run, the joined PDF and Word outputs were cut down to 3 pages apiece, though all six sample outputs were still produced; apply a license with License.Set before any of this runs unattended in production.


Getting Started in Your Back-Office Pipeline

You have one low-code document merging pattern working end to end, plus proof that the format check behind it runs on every call. Here’s where to go from here:

  • See the full picture: the companion use-case guide walks each of the sample’s six operations method by method, with a full FAQ.
  • Go deeper once a job grows: the full GroupDocs.Merger API covers more ground than this pattern needs the moment a pipeline outgrows one operation.
  • Browse the code: the complete sample on GitHub includes every input file used above.
  • Install it: the installation page covers the separate package and its supported target frameworks.
  • Remove the evaluation limits: get a temporary license before running any of this in production.
  • Questions: the support forum is where the team answers implementation questions.

Resources