Thursday, January 10, 2019

Merge a report exported into PDF with other PDF files

This is an example on how to alter the PDF created from a SSRS report with other PDF files (such as the ones attached as document attachments).

To be able to do this, we will need to download and install the PDFSharp library, which is an open source library to create or modify PDF files. First, download from www.pdfSharp.com, the extract the PdfSharp.dll and copy it to the bin folder of the model folder which you want to do the modification on. Second, from the Visual Studio, right click on the project, and add a reference into the .dll file.

As for the modification itself, we will need to override this delegate: SRSPrintDestinationSettingsDelegates.toSendFile() and make sure to set the result on the EventHandlerResult to false. This way, after calling the delegate, it will not continue with the rest of the codes in SrsReportRunPrinter.toFile(), which is going to save the report PDF file into the Azure storage or send it to the user.

The sample codes would look like this:

 using PdfSharp;  
 using Microsoft.Dynamics.ApplicationPlatform.SSRSReportRuntime.Instrumentation;  
 class SRSPrintDestinationSettingsDelegates_EventHandler  
 {  
   #SRSFramework  
   private static void saveFile(System.Byte[] reportBytes, SrsReportRunPrinter printer, SrsReportDataContract dataContract)  
   {  
     SRSPrintDestinationSettings printSettings = dataContract.parmPrintSettings();  
     //SRSReportFileFormat fileFormat = printSettings.fileFormat();  
     Filename filename = "";  
     if(printSettings.fileName())  
     {  
       filename = printSettings.fileName();  
     }  
     else  
     {  
       filename = dataContract.parmReportCaption() ? dataContract.parmReportCaption() + ".pdf" : dataContract.parmReportName() + ".pdf";  
     }  
     if (reportBytes)  
     {  
       System.IO.MemoryStream stream = new System.IO.MemoryStream(reportBytes);  
       // Send file to browser for on premise scenario.  
       if(SrsReportRunUtil::isOnPremEnvironment())  
       {  
         Dynamics.AX.Application.File::SendFileToUser(stream, filename);  
         SSRSReportRuntimeEventSource::EventWriteRenderReportToFileTaskStop(  
             "Printing report to file ended.",  
             dataContract.parmReportExecutionInfo().parmReportRunId());  
         return;  
       }  
       // Upload file to temp storage and direct the browser to the file URL  
       SrsFileUploadNameContract fileNameContract = new SrsFileUploadNameContract();  
       fileNameContract.FileName(filename);  
       str categoryName = SrsReportRunUtil::convertAndTrimGuidValue(dataContract.parmReportExecutionInfo().parmReportRunId());  
       fileNameContract.CategoryName(categoryName);  
       SRSFileUploadTempStorageStrategy fileUploader = new SRSFileUploadTempStorageStrategy();  
       fileUploader.uploadFile(stream, FormJsonSerializer::serializeClass(fileNameContract));  
       // Set global cache that indicates there is file uploaded for current report execution.  
       // Using SGC not SGOC because we want scope to be in current user session.  
       // Owner - #RunIdOwner macro, Key - RunId, Value - boolean value.  
       SysGlobalCache globalCache = classfactory.globalCache();  
       if(!globalCache.isSet(#RunIdOwner, dataContract.parmReportExecutionInfo().parmReportRunId()))  
       {  
         globalCache.set(#RunIdOwner, dataContract.parmReportExecutionInfo().parmReportRunId(), true);  
       }  
     }  
     SSRSReportRuntimeEventSource::EventWriteRenderReportToFileTaskStop(  
         "Printing report to file ended.",  
         dataContract.parmReportExecutionInfo().parmReportRunId());  
   }  
   [SubscribesTo(classStr(SRSPrintDestinationSettingsDelegates), delegateStr(SRSPrintDestinationSettingsDelegates, toSendFile))]  
   public static void SRSPrintDestinationSettingsDelegates_toSendFile(System.Byte[] reportBytes, SrsReportRunPrinter printer, SrsReportDataContract dataContract, Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[] paramArray, EventHandlerResult result)  
   {  
     Pdf.PdfDocument       outputPDFDocument = new Pdf.PdfDocument();  
     System.IO.MemoryStream   memoryStream = new System.IO.MemoryStream(reportBytes);  
     System.IO.MemoryStream   mergedStream = new System.IO.MemoryStream();  
     boolean           isOutputModified;  
     SRSPrintDestinationSettings printSettings = dataContract.parmPrintSettings();  
     System.Byte[]        finalReportBytes = reportBytes;  
     void addStream(System.IO.MemoryStream _stream, Pdf.PdfDocument _outputPDFDocument)  
     {  
       Pdf.PdfDocument inputPDFDocument = new Pdf.PdfDocument();  
       int       pageCount;  
       Pdf.PdfPages  pdfPages;  
       inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(_stream, PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);  
       _outputPDFDocument.set_Version(inputPDFDocument.get_Version());  
       pageCount = inputPDFDocument.get_PageCount();  
       pdfPages = inputPDFDocument.get_Pages();  
       if (pageCount > 0)  
       {  
         for (int idx = 0; idx < pageCount; idx++)  
         {  
           _outputPDFDocument.AddPage(pdfPages.get_Item(idx));  
         }  
       }  
     }  
     if (printSettings.fileFormat() == SRSReportFileFormat::PDF && dataContract.parmRdpName() == classStr(SalesInvoiceDP))  
     {  
       SalesInvoiceContract  salesInvoiceContract = dataContract.parmRdpContract();  
       DocuRef            docuRef;  
       int                 attachmentCounter;  
       try  
       {  
         new InteropPermission(InteropKind::ClrInterop).assert();  
         while select docuRef //add some criteria here  
         {  
           if (docuRef.fileType() == 'pdf')  
           {  
             if (!attachmentCounter)  
             {  
               addStream(memoryStream, outputPDFDocument);  
             }  
             System.IO.Stream docuStream = DocumentManagement::getAttachmentStream(docuRef);  
             memoryStream = new System.IO.MemoryStream();  
             docuStream.CopyTo(memoryStream);  
             addStream(memoryStream, outputPDFDocument);  
             attachmentCounter++;  
           }  
         }  
         if (attachmentCounter)  
         {  
           outputPDFDocument.Save(mergedStream, false);  
           finalReportBytes = mergedStream.ToArray();  
           result.result(false); //this is to force the SrsReportRunPrinter.toFile NOT to continue after calling this delegate  
           isOutputModified = true;  
         }  
         CodeAccessPermission::revertAssert();  
       }  
       catch(Exception::CLRError)  
       {  
         str errorMessage = AifUtil::getClrErrorMessage();  
         CodeAccessPermission::revertAssert();  
         throw error(errorMessage);  
       }  
     }  
     if (isOutputModified && finalReportBytes)  
     {  
       SRSPrintDestinationSettingsDelegates_EventHandler::saveFile(finalReportBytes, printer, dataContract);  
     }  
   }  
 }