Wednesday 14 February 2018

Render report to Bytes/memory stream in D365, Merge PDFs and Copy to Azure or Download in Local Machine

Client Requirement is to get a single PDF file of  random selection of invoices.
//Create a Project with C# class Library from VS and use below code 
//Code for Uploading Merged PDFs to Azure blob


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System.IO;
//using Microsoft.ServiceBus;
//using Microsoft.ServiceBus.Messaging;


namespace InvoiceBatchCopyToBlob
{

    public class UploadFileStream
    {
        public void printPaymentFile(byte[] fileStream, string _blobReference)
        {
            CloudStorageAccount storageAccount =
                CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=*****;AccountKey=********;EndpointSuffix=core.windows.net");

            //CloudConfigurationManager.GetSetting("StorageConnectionString"));

            // Create the blob client.
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

            // Retrieve a reference to a container.
            CloudBlobContainer container = blobClient.GetContainerReference("filestore");

            // Retrieve reference to a blob named "myblob".
            CloudBlockBlob blockBlob = container.GetBlockBlobReference(_blobReference);

            // Create or overwrite the "myblob" blob with contents from a local file.
            blockBlob.UploadFromByteArray(fileStream, 0, fileStream.Length);
            //this.triggerServiceBus();


        }
    }
}


 Note: Need to take reference of Azure storage to use Azure Libraries or else throws error.
 Add reference path- C# Class library Project -> Add references -> Manage Nuget packages-> Microsoft Azure.Storage-> Install. Once you install it will create a dll file in your bin folder of project. 



Create new AX project
//Create Dialog for selecting Invoices, create Report Bytes and upload to Azure Blob
Note: Add reference from above project to Ax project using path - AXproject->add references->Select reference of above created class. Also you need to take PDFSharp library reference, which is used to merge PDFs.


using CGlInvoiceBatchCopyToBlob;

public class CGLInvoiceBatchProcess extends Runbase
{
    DialogRunbase               dialog;
    DialogGroup                 dialogGrp;
    AccountNum                  accountNum;
    InvoiceId                   invoiceId;

    FormBuildStringControl      fbCtrlCustAccount, fbCtrlInvoice;
    FormStringControl           ctrlCustAccount, ctrlInvoice;

    container                   conCustAccount, conInvoice;
    SysLookupMultiSelectCtrl    msCustAccount, msInvoice;
   
   

    /// <summary>
    ///
    /// </summary>
    /// <returns></returns>
    public Object dialog()
    {
        FormBuildControl        formBuildControl;

        dialog  =   super();
        dialog.alwaysOnTop(true);
        dialog.windowType(FormWindowType::Standard);
        dialogGrp = dialog.addGroup("Select Invoices");
       
        formBuildControl = dialog.formBuildDesign().control(dialogGrp.formBuildGroup().id());
        
        fbCtrlInvoice    = formBuildControl.addControl(FormControlType::String, identifierStr(invoiceId));
        fbCtrlInvoice.label("Invoices");

        dialog.allowUpdateOnSelectCtrl(true);
        this.dialogSelectCtrl();


        return dialog;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name = "dialog"></param>
    public void dialogPostRun(DialogRunbase _dialog)
    {
        FormRun                 formRun;
        Query                   query;
        QueryBuildDataSource    qbds, qbdsJ, qbdsT;
        QueryBuildRange         qbr;
        SysTableLookup          sysLookup = new SysTableLookup();;
        super(dialog);

        CGLInvoiceBatchProcess process = new CGLInvoiceBatchProcess();

       
        formRun = _dialog.dialogForm().formRun();
        if (formRun)
        {
            ctrlInvoice     = formRun.design().control(fbCtrlInvoice.id());
            msInvoice       = SysLookupMultiSelectCtrl::construct(formRun, ctrlInvoice, querystr(CglInvoiceBatchDialogInvQuery), false, [tableNum(CustInvoiceJour), fieldNum(CustInvoiceJour, invoiceId)]);

         
        }

    }

    /// <summary>
    ///
    /// </summary>
    /// <returns></returns>
    public boolean getFromDialog()
    {
        #Characters

        conInvoice     = msInvoice.getSelectedFieldValues();
       
        info("Control 2 – " + con2StrUnlimited(conInvoice,#SEMICOLON));
        this.getReportBytes(conInvoice);

        return true;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name = "_args"></param>
    public static void main(Args _args)
    {
      
        CGLInvoiceBatchProcess           cglInvoiceBatchProcess = new CGLInvoiceBatchProcess();

        //Select Invoices dialog prompt
        if(cglInvoiceBatchProcess.prompt())
        {
            cglInvoiceBatchProcess.run();
        }

    }

    /// <summary>
    ///
    /// </summary>
    public void getReportBytes(container    _container)
    {
        DocuRef                         addedRecord;
        Filename                        fileName = "AbcTest.pdf";
        SalesInvoiceController                  controller = new SalesInvoiceController();
        SalesInvoiceContract                     contract = new SalesInvoiceContract();
        SRSPrintDestinationSettings     settings;
        Array                           arrayFiles;
        System.Byte[]                   reportBytes = new System.Byte[0]();
        System.Byte[]                   reportBytesUpload = new System.Byte[0]();
        SRSProxy                        srsProxy;
        SRSReportRunService             srsReportRunService = new SrsReportRunService();
        Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
        Map reportParametersMap;
        SRSReportExecutionInfo          executionInfo = new SRSReportExecutionInfo();
        Str                             strReport;
        int                             i,j;
        Container                       conBytes;
        Args                            _args = new Args();
        CustInvoiceJour                 custInvoiceJour;
        str                             blobReference;

        //UploadFileStream    uploadStream = new UploadFileStream();
       

        //Get report bytes for selected invoices
        for (i = 1; i<=conLen(_container);i++)
        {
           
            // Provide all the parameters to a contract
            custInvoiceJour.clear();
            select InvoiceId, RecId from custInvoiceJour where custInvoiceJour.InvoiceId == conPeek(_container, i);//'6004785Z-1';
            contract.parmRecordId(custInvoiceJour.RecId);
           
            // Provide details to controller and add contract
            controller.parmArgs(_args);
            controller.parmReportName(ssrsReportStr(SalesInvoice, ReportCGL_OrgWithLogo));
            controller.parmShowDialog(false);
            controller.parmLoadFromSysLastValue(false);
            controller.parmReportContract().parmRdpContract(contract);
            // Provide printer settings
            settings = controller.parmReportContract().parmPrintSettings();
            settings.printMediumType(SRSPrintMediumType::File);
            settings.fileName(fileName);
            settings.fileFormat(SRSReportFileFormat::PDF);

            // Below is a part of code responsible for rendering the report
            controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());
            controller.parmReportContract().parmReportExecutionInfo(executionInfo);

            srsReportRunService.getReportDataContract(controller.parmreportcontract().parmReportName());
            srsReportRunService.preRunReport(controller.parmreportcontract());
            reportParametersMap = srsReportRunService.createParamMapFromContract(controller.parmReportContract());
            parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);

            srsProxy            = SRSProxy::constructWithConfiguration(controller.parmReportContract().parmReportServerConfig());
            // Actual rendering to byte array
            reportBytes         = srsproxy.renderReportToByteArray(controller.parmreportcontract().parmreportpath(),
                                                      parameterValueArray,
                                                      settings.fileFormat(),
                                                      settings.deviceinfo());
            blobReference       = "Customer-" +custInvoiceJour.OrderAccount;

            //Upload report byte to Blob container
            //uploadStream.printPaymentFile(reportBytes, blobReference);

            strReport = System.Convert::ToBase64String(reportBytes);

            conBytes += strReport;
            //info(strReport);
        }
       
        //Merge PDFs
        this.mergePDFs(conBytes, blobReference);

    }

    /// <summary>
    ///
    /// </summary>
    public  void mergePDFs(container _con, str blobReference)
    {

        PdfSharp.Pdf.PdfDocument    outPutPDFDocument  = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfDocument    inputPDFDocument   = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfPages       pdfPages ;
        PdfSharp.Pdf.PdfPage        page;
        System.IO.MemoryStream      mergedFileStream   = new System.IO.MemoryStream() ;
        int                         i, j, pageCount;
        FileName                    pdfFile;

        InteropPermission           permission;

        str                         errorMessage;
        UploadFileStream            uploadStream = new UploadFileStream();
        str                         tempDir =  "K:\\tempDir";
      
        try
        {

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

            for (i = 1; i <= conLen(_con); i++)
            {
                System.Byte[]           reportByte;
                System.IO.MemoryStream  stream;

                reportByte  = System.Convert::FromBase64String(conPeek(_con,i));
                stream      = new System.IO.MemoryStream(reportByte);

                //pdfFile = conPeek(_con,i);

                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++)
                    {
                        page = pdfPages.get_item(j);
                        outputPDFDocument.AddPage(page);
                    }
                }
            }

            //System.IO.Directory::CreateDirectory(tempDir);
            outputPDFDocument.Save(mergedFileStream, false);
            //System.Byte[] outBytes = mergedFileStream.ToArray();

            //mergedFileStream = new System.IO.MemoryStream(outBytes);

           
            CodeAccessPermission::revertAssert();


        }

        catch(Exception::CLRError)
        {

            // Get the CLR error before any other CLR operation

            errorMessage = AifUtil::getClrErrorMessage();
            CodeAccessPermission::revertAssert();
            throw error(errorMessage);
        }
        //Upload mergedFileStream to Azure Blob
        uploadStream.printPaymentFile(mergedFileStream, blobReference);
/* If you want to store the file in local machine and for Popup to Open/Save/Saveas Option
    str                         fileUrl;
       
    FileUploadTemporaryStorageStrategy  fileUploadStrategy = new       FileUploadTemporaryStorageStrategy();
    FileUploadTemporaryStorageResult result =     fileUploadStrategy.uploadFile(mergedFileStream, "Test", "", ".pdf") as    FileUploadTemporaryStorageResult;

        if (result && result.getUploadStatus())
        {
            fileUrl = result.getDownloadUrl();
            info(fileUrl);

            new Browser().navigate(fileUrl);

        }
  */
    }


}


Reference Links: http://www.meritsolutions.com/microsoft-dynamics-365/render-report-memory-stream-d365-aka-ax7/

http://axaptamasters.com/print-multiple-invoices-one-pdf-file-ax7/

https://dynamicsaxgyan.wordpress.com/?s=Tutorial_LookUpMultiSelectDialog

http://dev.goshoom.net/en/2016/03/file-upload-and-download-in-ax-7/

http://daxtechies.blogspot.in/2017/07/dynamics-365-for-operations-pack-your.html