Published On: January 11, 2017Categories: Technical

The Problem

One of the improvements that we had to do for one of our clients was the possibility to select multiple invoices from the grid control and then print them all into one PDF, instead of the default action of creating a separate PDF file per invoice.
print multiple invoices into one pdf
In our particular case, the print destination settings are maintained for all the invoices and confirmed on the press of the OK button.

print destination settings

The Solution

To perform the operation, we had to extend the system SRSReportRunController class and to create an additional method “runFromArgs(Args _args)” where we replicated all the steps that are performed when the report is run, with the purpose of getting the rendered report bytes for the selected invoices. The next step was to put the data into a container and use the merging method where the container is being read as a stream and afterwards written to a file and sent to the user.
In this process, we used the PDFsharp library (PDRSharp.dll) which needed to be added as a reference to the project.
public static void runFromArgs(Args _args)
{
if (!_args || _args.dataset() != tableNum(InvoiceTable))
{
            throw error(“@SYS22996”);
}

InvoiceTable invoice = _args.record() as InvoiceTable;

Map selectedRecordMap =          Map::create(Helpers::getFormRecordOrderedByField(FormDataUtil::getFormDataSource(invoice), fieldNum(InvoiceTable,InvoiceNum)));

MapEnumerator mapEnum = selectedRecordMap.getEnumerator();

boolean firstReport = true;
boolean printMediumFile;
container con;
str mergedFileName;
boolean proceed = true;
SRSPrintMediumType firstMediumType;

while (mapEnum.moveNext())
{
   PdfSharp.Pdf.PdfDocument initialDocument;
   invoice = mapEnum.currentValue();
           ExtendedInvoiceReportController controller = new ExtendedInvoiceReportController();
           controller.parmReportName(invoice.PSOInvoiceLayoutTable().ReportName);
           _args.record(invoice);
           controller.parmArgs(_args);
   controller.parmShowDialog(firstReport==true);

   SrsReportDataContract reportContract = controller.parmReportContract();
   SRSReportExecutionInfo executionInfo;
   guid reportRunId = newGuid();
   executionInfo = new SRSReportExecutionInfo();
   executionInfo.parmReportRunId(reportRunId);
   reportContract.parmReportExecutionInfo(executionInfo);
           reportContract.parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());

   SRSPrintDestinationSettings printSettings;
   printSettings = reportContract.parmPrintSettings();

   if(firstReport)
   {
controller.prompt();
   }
   else
   {
printSettings.printMediumType(firstMediumType);
printSettings.fileFormat(SRSReportFileFormat::PDF);
printSettings.overwriteFile(true);
   }
   if(controller.dialogCanceled && firstReport == true)
   {
proceed = false;
firstReport = false;
   }
   if(proceed)
   {
             if(printSettings.printMediumType() == SRSPrintMediumType::File)
             {
                 firstMediumType = printSettings.printMediumType();
                 controller.runReport();

Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
Map reportParametersMap;
rsReportRunPrinter reportRunPrinter;

SrsReportRunService service = new SrsReportRunService();
service.getReportDataContract(invoice.InvoiceLayoutTable().ReportName);
service.preRunReport(reportContract);

reportParametersMap = service.createParamMapFromContract(reportContract);

parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
reportRunPrinter = new SrsReportRunPrinter(reportContract, parameterValueArray);

SRSReportFileFormat fileFormat = printSettings.fileFormat();
System.Byte[] reportBytes;

SRSProxy proxy = reportRunPrinter.parmSrsProxy();
                reportBytes = proxy.renderReportToByteArray(reportContract.parmReportPath(),
                                                  parameterValueArray,
                                                  fileFormat,
                                                  printSettings.deviceInfo());

                str content = System.Convert::ToBase64String(reportBytes);

                if(firstReport)
                {
                           mergedFileName = printSettings.parmFileName();
                }
                con += content;
                firstReport = false;

                printMediumFile = true;

                }
                else
                {
                   irstMediumType = printSettings.printMediumType();
                   controller.runOperation();
                   firstReport = false;
                }
             }
         }
         if(printMediumFile)
        {
           Helper::mergePDFs(con, mergedFileName);
        }

  }

The Method

Further on, the actual method for merging of the PDFs is as follows:

   static PdfSharp.Pdf.PdfDocument mergePDFs(container _con, str _mergedName)
    {
        PdfSharp.Pdf.PdfDocument outPutPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfDocument inputPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfPages pdfPages;
        container con = _con;
        int i, j, pageCount;
        FileName pdfFile;
        InteropPermission permission;
        str errorMessage;
        System.IO.MemoryStream mergedStream;
        str tempDir;
        str mergedName = ‘INVOICES_’ + _mergedName;

        try
        {
            permission = new InteropPermission(InteropKind::ClrInterop);
            permission.assert();

            for (i = 1; i <= conLen(con); i++)
            {

                System.Byte[] reportBytes;
                System.IO.MemoryStream stream;

                reportBytes = System.Convert::FromBase64String(conPeek(con,i));

                stream  = new System.IO.MemoryStream(reportBytes);
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 (j = 0 ; j < pageCount; j++)
                    {
                        outputPDFDocument.AddPage(pdfPages.get_Item(j));
                    }
                }
            }
            new InteropPermission(InteropKind::ClrInterop).assert();

    tempDir = ‘C:\\tempDir’;
            System.IO.Directory::CreateDirectory(tempDir);

            CodeAccessPermission::revertAssert();
            outputPDFDocument.Save(tempDir + ‘\\’ + mergedName);
            System.Byte[] outBytes = System.IO.File::ReadAllBytes(tempDir + ‘\\’ + mergedName);
            mergedStream = new System.IO.MemoryStream(outBytes);
            File::SendFileToUser(mergedStream, mergedName);
            CodeAccessPermission::revertAssert();

            new InteropPermission(InteropKind::ClrInterop).assert();
            PSOHelpers::clearDirectory(tempDir);
            System.IO.Directory::Delete(tempDir);
            CodeAccessPermission::revertAssert();
        }
        catch(Exception::CLRError)
        {
            errorMessage = AifUtil::getClrErrorMessage();
            CodeAccessPermission::revertAssert();
            throw error(errorMessage);
        }
        return outputPDFDocument;
    }

    static void clearDirectory(Filename _folderName)
    {
        System.String[] files;
        int             filecount;
        int             i;

        if (WinAPI::folderExists(_folderName))
        {
            files = System.IO.Directory::GetFiles(_folderName);
            filecount = files.get_Length();

            for(i=0; i < filecount; i++)
            {
                System.IO.File::Delete(files.get_Item(i));
            }

        }

   }

Side Effects

As a side effect, we faced the problem that extra PDF files were being created for all the invoice reports that were additionally going to be sent to the client along with the merged file, which would be pretty annoying for the client.
We managed to resolve that problem with the use of a post handler for the “onToFile” method from the SRSPrintDestinationSettingsDelegates class, where the method was set to not return a value in a set condition.

[PostHandlerFor(classStr(SRSPrintDestinationSettingsDelegates), methodStr(SRSPrintDestinationSettingsDelegates, onToFile))]
public static void SRSPrintDestinationSettingsDelegates_Post_onToFile(XppPrePostArgs args)
{
        ProjectHoursContract projectHoursContract;
        SrsReportDataContract dataContract;

        dataContract = args.getArgNum(2);
        projectHoursContract = dataContract.parmRdpContract() as ProjectHoursContract;

        if(!projectHoursContract)
        {
            args.setReturnValue(false);
        }
}

  1. c6be3369fb4186496ca3f40bd9e80645?s=54&d=mm&r=g
    Nishant Srivastava March 28, 2017 at 10:57 am - Reply

    How to use the method runFromArgs() ? From where exactly we’ll make the call to this method?