Search code examples
c#asp.net-mvcpdfcrystal-reportsitext7

Merging Crystal Reports with itext7 in MVC produces unmerged pdf from stream


I'm trying to merge two (or more) Crystal Reports in an ASP.net MVC project and I downloaded the itext7 NuGet package to do so. I'm trying to put together a simple proof-of-concept in which I concatenate a pdf with itself in a single method:

var rpt1 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
var rpt2 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();

rpt1.Load(Server.MapPath("~/Reports/MyReport.rpt");
rpt2.Load(Server.MapPath("~/Reports/MyReport.rpt");

DataTable table = GetDataMethod();
rpt1.SetDataSource(table);
rpt2.SetDataSource(table);

Stream stream = rpt.ExportToStream(ExportFormatType.PortableDocFormat);
var write = new PdfWriter(stream);
var doc = new PdfDocument(write);
var merger = new PdfMerger(doc);

var doc1 = new PdfDocument(new PdfReader(rpt1.ExportToStream(ExportFormatType.PortableDocFormat)));
var doc2 = new PdfDocument(new PdfReader(rpt2.ExportToStream(ExportFormatType.PortableDocFormat)));

merger.Merge(doc1, 1, doc1.GetNumberOfPages());
merger.Merge(doc2, 1, doc2.GetNumberOfPages());
doc.CopyPagesTo(1, doc2.GetNumberOfPages(), doc2);

stream.Flush();
stream.Position = 0;
return this.File(stream, "application/pdf", "DownloadName.pdf");

You can see I'm sort of throwing everything at the wall and seeing what sticks insofar as I'm using both PdfMerger.Merger() and PdfDocument.CopyPagesTo(), and I think either of those should be sufficient to do the job by itself? (And, of course, I ran the code trying each of those by themselves as well as together.) But when I run the above code the PDF which downloads is unmerged, i.e. the report only appears once. (If I run it with two different reports, then only the first report appears.)

Now, I'm returning the stream while I'm doing all the interesting stuff with the PdfMerger and PdfDocument objects, so it makes sense to me that the stream would be unchanged. But all the examples of using iText 7 I've found return either the stream or a byte array (e.g., this StackOverflow question), so that seems to be the way it is supposed to work.

Any changes I've made to the code either have no effect, throw an error, or result in the downloaded file being unreadable by the browser (i.e. not recognized as a PDF). For example, I tried converting the stream to a byte array and returning that:

using (var ms = new MemoryStream()) {
     stream.CopyTo(ms);
     byte[] bytes = ms.ToArray();
     return new FileContentResult(bytes, "application/pdf");
}

but the browser couldn't open the download then. The same thing happened when I tried closing the PdfDocument before returning the stream (trying it to force it to write the merge to the stream).


Solution

  • There is a lot of confusion with streams in your code. Normally a stream is used either for input or for output. MemoryStream can be used for both, but you need to make sure to not close it to be able to reuse it. It's often simpler and cleaner to create a new instance with the underlying bytes than reusing existing ones, especially taking into account that it does not affect the performance much as the underlying heavy array structures will be reused by new instances anyway. Here is an example of how yo distinguish between the streams. ExportToStream returns you a stream from which you can obtain the byte array with the bytes of your PDF files, then you load those documents into iText and you also create the third document that you will merge the two source documents into. Then you have to make sure to call PdfDocument#Close() to tell iText to finalize your documents and then you can fetch the resultant bytes of the merged document and pass them along, wrapping them into a stream if necessary

    var rpt1 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
    var rpt2 = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
    
    rpt1.Load(Server.MapPath("~/Reports/MyReport.rpt");
    rpt2.Load(Server.MapPath("~/Reports/MyReport.rpt");
    
    DataTable table = GetDataMethod();
    rpt1.SetDataSource(table);
    rpt2.SetDataSource(table);
    
    var report1Stream = (MemoryStream)rpt1.ExportToStream(ExportFormatType.PortableDocFormat);
    var report2Stream = (MemoryStream)rpt2.ExportToStream(ExportFormatType.PortableDocFormat);
    
    var doc1 = new PdfDocument(new PdfReader(new MemoryStream(report1Stream.ToArray())));
    var doc2 = new PdfDocument(new PdfReader(new MemoryStream(report2Stream.ToArray())));
    
    var outStream = new MemoryStream();
    var write = new PdfWriter(outStream);
    var doc = new PdfDocument(write);
    var merger = new PdfMerger(doc);
    
    merger.Merge(doc1, 1, doc1.GetNumberOfPages());
    merger.Merge(doc2, 1, doc2.GetNumberOfPages());
    
    doc.Close();
    doc1.Close();
    doc2.Close();
    
    return this.File(new MemoryStream(outStream.ToArray()), "application/pdf", "DownloadName.pdf");