If you need to generate a SSRS report as a byte array, you can use a utility X++ class we created for some custom scenarios such as sending Sales and Project invoice in the single email to the corresponding customer. We will explain this particular scenario in our next article.
Generate Non-Print management report
Let’s take a look how we can generate the content of a Non-Print management report such as Number of workers using DocSrsReportGenerator, a helper class for generating SSRS reports in memory, i.e. as D365FO containers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public container printNumberOfWorkersToContainer() { // Instance the report controller and contract. SrsReportRunController controller = new SrsReportRunController(); controller.parmReportName(ssrsReportStr(HcmNumberOfWorkersReport, Report)); controller.parmArgs(new Args()); HcmNumberOfWorkersContract contract = controller.parmReportContract().parmRdpContract(); contract.parmAsofDate(today()); // Generate the report. container generatedReport = new DocSrsReportGenerator(controller).generateSsrsReport(SRSReportFileFormat::PDF); return generatedReport; } |
We can use the same class, DocSrsReportGenerator, to generate reports using Docentric templates and Docentric rendering engine as well.
// Generate Docentric report using Default template.
container generatedReport =
new DocSrsReportGenerator(controller).generateDocentricReport(DocOutputFileFormat::DOCX);
In Docentric Free Edition you will also find useful utility methods, e.g. for converting D365FO containers to .NET byte arrays or memory streams.
// Convert container to a byte array.
System.Byte[] generatedReportByteArray =
DocGlobalHelper::convertContainerToBytes(generatedReport);
// Convert container to a memory stream.
System.IO.MemoryStream generatedReportMemoryStream =
DocGlobalHelper::convertContainerToMemoryStream(generatedReport);
Generate Print management report
We will now demonstrate how to generate a Print management report as a byte array on the example of Sales invoice. This code is a bit more complex, since we have to involve the SalesInvoiceJournalPrint class in order to avoid printing using Print management.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public container printSalesInvoiceReportToContainer(boolean _useSsrsReportDesign = false) { Args args = new Args(); SalesInvoiceController salesInvoiceController = SalesInvoiceController::construct(); // Set the SalesInvoice.Report as SSRS Report Design. salesInvoiceController.parmReportName( PrintMgmtDocType::construct(PrintMgmtDocumentType::SalesOrderInvoice).getDefaultReportFormat()); // In order to have the context table we need to set args.record(). // We will just select the first posted invoice. CustInvoiceJour custInvoiceJour; select custInvoiceJour where custInvoiceJour.SalesId != ''; args.record(custInvoiceJour); salesInvoiceController.parmArgs(args); // Set the report data contract with parameters. SalesInvoiceContract salesInvoiceContract = salesInvoiceController.parmReportContract().parmRdpContract(); salesInvoiceContract.parmRecordId(custInvoiceJour.RecId); DocSrsReportGenerator reportGenerator = new DocSrsReportGenerator(salesInvoiceController); reportGenerator.setPrintDestinationSettings_SsrsReport(SRSReportFileFormat::PDF); // NOTE: If you want to generate the report using a Docentric template, use this line of code instead. // reportGenerator.setPrintDestinationSettings_DocentricReport(DocOutputFileFormat::PDF); // Initalize SalesInvoiceJournalPrint class instance because there is no other way // NOT to use Print Management. SalesInvoiceJournalPrint salesInvoiceJournalPrint = SalesInvoiceJournalPrint::construct(); salesInvoiceJournalPrint.parmPrintFormletter(NoYes::Yes); salesInvoiceJournalPrint.parmUsePrintManagement(false); salesInvoiceJournalPrint.parmUseUserDefinedDestinations(true); salesInvoiceJournalPrint.parmPrinterSettingsFormLetter( salesInvoiceController.parmReportContract().parmPrintSettings().pack()); args.caller(salesInvoiceJournalPrint); args.parmEnumType(enumNum(PrintCopyOriginal)); args.parmEnum(PrintCopyOriginal::OriginalPrint); // Start the report execution and wait until the report content is not generated. container generatedSalesInvoice = reportGenerator.generateReport(); return generatedSalesInvoice; } |
How DocSrsReportGenerator works
The key method in the DocSrsReportGenerator class is startOperationSynchronouslyAndGetReportContent(), in which the report controller is configured to run synchronously, without showing the report dialog and loading parameters from SysLastValue.
At the beginning of the method we subscribed to the renderingCompleted event, so when controller.startOperation() is invoked, it will block the execution until the renderingComplete event handler completes.
Note that in the renderingComplete event handler we will store the reference to the report content into an internal field, reportContentAsContainer. This is possible because we extended the SRSReportExecutionInfo object to contain the generated report content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
protected container startOperationSynchronouslyAndGetReportContent() { try { // Register event handler for the RenderingComplete event to get the report content. controller.renderingCompleted += eventhandler(this.renderingComplete); // Report execution mode has to be Synchronous mode in order to block the execution until the report is not generated. controller.parmExecutionMode(SysOperationExecutionMode::Synchronous); // Don't show the report dialog. controller.parmShowDialog(false); // Do not use and save the report last parameter values. controller.parmLoadFromSysLastValue(false); // Start the report execution; it will wait until the controller completes, i.e. until // the renderingComplete event handler, which sets reportContentAsContainer, completes. controller.startOperation(); // Unregister the event handler for the RenderingComplete event. controller.renderingCompleted -= eventhandler(this.renderingComplete); } catch(Exception::CLRError) { DocGlobalHelper::handleClrException(funcName(), this.getErrorMessage()); } catch { DocGlobalHelper::handleException(funcName(), this.getErrorMessage()); } return reportContentAsContainer; } |
As mentioned earlier, in the renderingComplete event handler we can access to the generated document using the SRSReportExecutionInfo object extended with Docentric properties. In fact, to generate report as a byte array, we used a hidden Docentric print destination called Memory, whose purpose is just that – to generate a report in memory in the wanted output format.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
protected void renderingComplete(SrsReportRunController _sender, SrsRenderingCompletedEventArgs _eventArgs) { try { // Get the report execution info. DocReportExecutionInfo reportExecutionInfo = _eventArgs.parmReportExecutionInfo().parmReportExecutionInfo_DC(); DocPrintReportToMemoryExecutionInfo printDestMemoryExecutionInfo = reportExecutionInfo.parmPrintToMemoryExecutionInfo(); // Convert the report content from the memory stream to a container. using (System.IO.MemoryStream reportMemoryStream = printDestMemoryExecutionInfo.getReportContentAsMemoryStream()) { reportContentAsContainer = DocGlobalHelper::convertMemoryStreamToContainer(reportMemoryStream); } } catch(Exception::CLRError) { DocGlobalHelper::handleClrException(funcName(), strFmt('Retrieving report content failed. %1.', this.getErrorMessage())); } catch { DocGlobalHelper::handleException(funcName(), strFmt('Retrieving report content failed. %1.', this.getErrorMessage())); } } |
If you find the DocSrsReportGenerator class handy, just download Docentric Free Edition and start using it!
In our next article, you can see how to use it to send Sales and Project invoice in the single email.
Read the article >>