Merge Two or More PDF Files in C# Using iTextSharp

How to merge PDF files in C#

Version scope: This example uses iTextSharp (iText 5), a legacy library. Its APIs differ from current iText releases. For new projects, review the official iText version and support guidance and the applicable licensing before choosing a package.

To merge two or more PDF files into one using iTextSharp in C#, you create a Document, attach a PdfCopy writer to an output file, open the document, and copy every page of every input PDF into it. In this article you'll build a reusable MergeMultiplePDF method that does exactly that. Then you'll use it in a real scenario: exporting several Crystal Reports to PDF and combining them into a single file.

By the end, you'll know what iTextSharp is, how to add it to a project, how the merge method works line by line, and what to check when it fails.

Merge Multiple PDF Files Into Single PDF Using Itextsharp in C#

The problem this solves

Reporting features often export to several formats: PDF, DOC, XLSX, CSV, RPT, and more. Sometimes a client wants more than one export, and they want all of it as a single document.

Suppose you export reports from "Crystal Report Viewer" to PDF, and you end up with a pile of separate files. The client wants to print all of them with one click, which means merging them into one PDF first.

I ran into this requirement recently. I posted it on the C# Corner forum and searched many websites, but nothing fit. A senior colleague suggested a simple approach: loop over the data, export each report to a PDF in a temporary folder, merge those files with iTextSharp, and delete the temporary files. That approach worked, and it's what this article walks through.

What is iTextSharp?

iTextSharp is the .NET port of the iText 5 library. It lets you create, read, and modify PDF files from C# code, including converting simple HTML or page output into PDF and merging existing PDFs.

Two things to know before you use it:

  • iTextSharp 5.x is licensed under the AGPL. If you use it in a closed-source commercial application, read the license terms or buy a commercial license from iText.
  • It's the older generation of the library. Newer projects usually use iText 7 or later, which has a different API (for example, PdfMerger). The code in this article targets iTextSharp 5.x.

Requirements

The solution needs to do three things:

  1. Export multiple Crystal Reports to PDF with one click and save them in a temporary folder inside the project directory.
  2. Merge all exported PDFs into a single PDF.
  3. Remove the temporary exported files and any old merged file left over from a previous run.

Implementation

Prerequisites

  • A .NET Framework project in Visual Studio (Crystal Reports for .NET Framework requires it).
  • iTextSharp 5.x added through NuGet. Open the Package Manager Console and run Install-Package iTextSharp. Avoid downloading itextsharp.dll from random websites, since you can't verify what's inside.
  • For the second example, the Crystal Reports runtime and a .rpt report file in your project.
  • Basic C# knowledge: loops, arrays, and using blocks.

Step 1: Create the merge method

Add this method to your code-behind or to a helper class. It takes an array of PDF file paths and the path of the file to create.

C#
public static void MergeMultiplePDF(string[] PDFfileNames, string OutputFile)
{
    // Create the document object
    iTextSharp.text.Document PDFdoc = new iTextSharp.text.Document();
    // The FileStream is disposed automatically at the end of the using block
    using (System.IO.FileStream MyFileStream = new System.IO.FileStream(OutputFile, System.IO.FileMode.Create))
    {
        // PdfCopy writes the imported pages into the output document
        iTextSharp.text.pdf.PdfCopy PDFwriter = new iTextSharp.text.pdf.PdfCopy(PDFdoc, MyFileStream);
        // Open the document before adding pages
        PDFdoc.Open();
        foreach (string fileName in PDFfileNames)
        {
            // Create a reader for one input PDF
            iTextSharp.text.pdf.PdfReader PDFreader = new iTextSharp.text.pdf.PdfReader(fileName);
            try
            {
                PDFreader.ConsolidateNamedDestinations();
                // Copy every page (page numbers start at 1)
                for (int i = 1; i <= PDFreader.NumberOfPages; i++)
                {
                    iTextSharp.text.pdf.PdfImportedPage page = PDFwriter.GetImportedPage(PDFreader, i);
                    PDFwriter.AddPage(page);
                }
                // Keep form fields if the input PDF has any
                iTextSharp.text.pdf.PRAcroForm form = PDFreader.AcroForm;
                if (form != null)
                {
                    PDFwriter.CopyAcroForm(PDFreader);
                }
            }
            finally
            {
                // Always release the input file, even if copying fails
                PDFreader.Close();
            }
        }
        // Closing the document also closes the PdfCopy writer
        PDFdoc.Close();
    }
}

How the method works

The method has two parameters. PDFfileNames is a string array holding the paths of the input PDFs, and OutputFile is the path of the merged file.

PdfCopy is the class that does the merging. It's built to copy pages from existing PDFs, which is why it's used here instead of PdfWriter. For each input file, a PdfReader opens the source, GetImportedPage grabs a page, and AddPage appends it to the output. The files are merged in the order they appear in the array.

Expected result: after the method returns, OutputFile exists and its page count equals the total pages of all input files.

Step 2: Use the method with exported Crystal Reports

Now let's use the method in a real workflow. The flowchart below shows the order of operations: export one PDF per grid row, merge them, open the result, and clean up.

Start
Repeat for each grid row
Read JobId from row j and get its DataTable
Decision: Rows found?
No: skip the export and go to "More rows?"
Yes
Export the report to a temp PDF file
Decision: More rows?
Yes: go back to "Read JobId" with the next row
No (all rows done)
Merge the temp PDFs into one file
Open the merged PDF
Delete the temp PDFs
End
Each grid row becomes one temporary PDF. When the loop finishes, the PDFs are merged into one file, opened, and then deleted.

In short: the program reads a JobId from each grid row and loads its data. If the row has data, it exports a temporary PDF. After the last row, it merges the PDFs, opens the result, and deletes the temporary files.

The code below follows that flow. It assumes a WinForms app with a DataGridView named dataGridView1 that has a JobId column, and the usual using directives for System.IO, System.Data, System.Diagnostics, System.Collections.Generic, CrystalDecisions.CrystalReports.Engine, and CrystalDecisions.Shared.

C#
List<string> pdfFiles = new List<string>();
string tempFolder = Path.Combine(Application.StartupPath, "Temp");
string mergedFile = Path.Combine(tempFolder, "Codingvila_Articles_Report.pdf");

try
{
    // Make sure the temp folder exists
    Directory.CreateDirectory(tempFolder);

    // Create one PDF per grid row
    for (int j = 0; j < dataGridView1.Rows.Count; j++)
    {
        int jobId = Convert.ToInt32(dataGridView1.Rows[j].Cells["JobId"].Value);
        // Return the data for this job
        DataTable dtDetail = new My_GlobalClass().GetDataTable(jobId);

        if (dtDetail.Rows.Count > 0)
        {
            ReportDocument cryRpt = new ReportDocument();
            try
            {
                // Load the .rpt file and assign the data source
                string rptPath = Path.Combine(Application.StartupPath, "RPT", "rptCodingvila_Articles_Report.rpt");
                cryRpt.Load(rptPath);
                cryRpt.SetDataSource(dtDetail);

                // Export the report to a temp PDF and remember its path
                string pdfPath = Path.Combine(tempFolder, "Codingvila_Articles_Report" + jobId + ".pdf");
                cryRpt.ExportToDisk(ExportFormatType.PortableDocFormat, pdfPath);
                pdfFiles.Add(pdfPath);
            }
            finally
            {
                // Release Crystal Reports resources for this report
                cryRpt.Close();
                cryRpt.Dispose();
            }
        }
    }

    if (pdfFiles.Count > 0)
    {
        // Remove an old merged file if one is left over
        if (File.Exists(mergedFile))
        {
            File.Delete(mergedFile);
        }

        // Merge all temp PDFs into one file
        MergeMultiplePDF(pdfFiles.ToArray(), mergedFile);

        // Open the merged PDF
        Process.Start(mergedFile);
    }
}
catch (Exception ex)
{
    XtraMessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
    // Delete the temp PDFs, even if something above failed
    foreach (string item in pdfFiles)
    {
        if (File.Exists(item))
        {
            File.Delete(item);
        }
    }
}

What the code does

The loop reads the JobId value from each grid row. It passes that ID to GetDataTable, a method in the author's My_GlobalClass that returns a DataTable. Replace it with your own data-access code.

For each row that returns data, the code creates a ReportDocument, loads rptCodingvila_Articles_Report.rpt from the RPT folder, and assigns the data with SetDataSource. ExportToDisk then writes a PDF to the Temp folder, and the path goes into the pdfFiles list. The report viewer control isn't needed for exporting, so those lines are gone.

After the loop, if at least one PDF was exported, the code deletes any old merged file, calls MergeMultiplePDF, and opens the result. The finally block removes the temporary PDFs whether or not the merge succeeded, and the merged file stays because it isn't in the list.

Expected result: the default PDF viewer opens Codingvila_Articles_Report.pdf, which contains every report in grid order. The Temp folder then holds only that file.

Common errors and how to fix them

  • DirectoryNotFoundException when exporting: the Temp folder doesn't exist. The code above creates it with Directory.CreateDirectory.
  • IOException saying the file is in use: the merged PDF is still open in a viewer, so it can't be deleted or overwritten. Close it and run the code again.
  • InvalidPdfException when creating a PdfReader: one of the input files is empty or corrupted. Check that each export finished and that the file opens in a PDF viewer.
  • Every page is the same report: this usually means the loop reads a fixed row such as Rows[0] instead of Rows[j].

Limitations to keep in mind

This approach copies pages and form fields. Check that other features your PDFs rely on, such as bookmarks, survive the merge before you ship it. If your project uses a newer iText version, the merge code will differ, because iText 7 and later have a different API.

Summary

To merge multiple PDF files into one in C#, open a Document, attach a PdfCopy writer, and add each input page with GetImportedPage and AddPage. Key takeaways:

  • Install iTextSharp from NuGet and check the AGPL license before using it in commercial software.
  • Use PdfCopy for merging, and close each PdfReader in a finally block.
  • When exporting Crystal Reports in a loop, dispose each ReportDocument and clean up temp files in a finally block.
  • Index the grid row with the loop variable, and create the temp folder before you export.

Codingvila provides articles and blogs on web and software development for beginners as well as free Academic projects for final year students in Asp.Net, MVC, C#, Vb.Net, SQL Server, Angular Js, Android, PHP, Java, Python, Desktop Software Application and etc.

avatar

Thanks for your valuable suggestions Great, and sure I will try it.

delete February 14, 2019 at 10:27:00 PM GMT+5:30
avatar
Unknown

I simply use ZETPDF to generate PDF reports and I think ZETPDF is the best solution for generting PDF reports.

delete February 19, 2019 at 3:45:00 PM GMT+5:30

If you have any questions, contact us on info.codingvila@gmail.com