How to Store SO Invoices PDF to Azure Blob Storage Container using X++ Code Approach

Scenario:  

Imagine you have to store files in online storage, so you can access them on the Internet at any time and anywhere. 
In my case, I’m using an Azure storage account to store and access the D365 finance and operation Sales order invoice files. 

Introduction: 

Storage account: Azure is a resource used to store and manage data. It provides a secure and scalable way to store various data types, such as blobs, files, queues, and tables. A storage account enables access to these data on the internet. 

Container: Within a storage account (specifically in Blob Storage) is a logical grouping of objects (blobs). Containers are used to organize and manage blobs in a storage account. Each container can hold multiple blobs, and containers provide access control and management capabilities, including setting permissions for the blobs inside them. 

High level resolution steps 

    • Take the SO invoice details from CustInvoiceJour table. 
    • Using SO invoice details run the SO invoice report. 
    • Convert report PDF into memory stream. 
    • Insert memory stream to azure blob storage container. 

    Detailed resolution steps 

      Code Snippet: 

      internal final class SCCSalesInvoiceToBlob 
      
      { 
      
       
      
          ///  
      
          /// Class entry point. The system will call this method when a designated menu  
      
          /// is selected or when execution starts and this class is set as the startup class. 
      
          ///  
      
          /// The specified arguments. 
      
          public static void main(Args _args) 
      
          { 
      
              SCCSalesInvoiceTrackingTable salesInvoiceTrackingTable; 
      
              CustInvoiceJour custInvoiceJour; 
      
       
      
       
      
              select firstonly custInvoiceJour 
      
                  join salesInvoiceTrackingTable 
      
                  where custInvoiceJour.RecId == salesInvoiceTrackingTable.CustInvoiceJourRecId 
      
                  && salesInvoiceTrackingTable.Processed == NoYes::No; 
      
               
      
              if(custInvoiceJour.RecId) 
      
              { 
      
                  SCCSalesInvoiceTrackingTable soInvoiceTrackingTable; 
      
                  Args                        args; 
      
                  SalesInvoiceController      controller = SalesInvoiceController::construct(); 
      
                  SalesInvoiceContract        contract = new SalesInvoiceContract(); 
      
                  SRSReportExecutionInfo      executionInfo = new SRSReportExecutionInfo(); 
      
                  SRSPrintDestinationSettings settings; 
      
                  Map                         reportParametersMap; 
      
                  SRSReportRunService         srsReportRunService = new SrsReportRunService(); 
      
                  SRSProxy                    srsProxy; 
      
                  System.Byte[]               reportBytes = new System.Byte[0](); 
      
                  ParameterValue[]            parameterValueArray; 
      
                  System.IO.Stream            stream; 
      
       
      
                  args = new Args(); 
      
                  args.record(custInvoiceJour); 
      
                  contract.parmRecordId(custInvoiceJour.RecId); 
      
                  controller.parmArgs(args); 
      
                  controller.parmReportName(ssrsReportStr(SalesInvoice, Report)); 
      
                  controller.parmShowDialog(false); 
      
                  controller.parmLoadFromSysLastValue(false); 
      
                  controller.parmReportContract().parmRdpContract(contract); 
      
                  controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration()); 
      
                  controller.parmReportContract().parmReportExecutionInfo(executionInfo); 
      
       
      
                  settings = controller.parmReportContract().parmPrintSettings(); 
      
                  //settings = this.setPrintSettings(settings); 
      
                  settings.printMediumType(SRSPrintMediumType::File); 
      
                  settings.fileFormat(SRSReportFileFormat::PDF); 
      
       
      
                  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()); 
      
       
      
                  if (reportBytes) 
      
                  { 
      
                      stream = new System.IO.MemoryStream(reportBytes); 
      
                  } 
      
                  Filename fileName = strFmt('SalesInvoiceReport_%1_%2',custInvoiceJour.SalesId,custInvoiceJour.InvoiceId); 
      
                  str soInvoicepath = SCCSalesInvoiceToBlob::buildConnectionToAzure(stream, filename); 
      
       
      
                  select soInvoiceTrackingTable 
      
                      where soInvoiceTrackingTable.CustInvoiceJourRecId == custInvoiceJour.RecId; 
      
       
      
                  if(soInvoiceTrackingTable.RecId) 
      
                  { 
      
                      soInvoiceTrackingTable.selectForUpdate(true); 
      
                  } 
      
                   
      
                  soInvoiceTrackingTable.CustInvoiceJourRecId = custInvoiceJour.RecId; 
      
                  soInvoiceTrackingTable.SalesId = custInvoiceJour.SalesId; 
      
                  soInvoiceTrackingTable.InvoiceId = custInvoiceJour.InvoiceId; 
      
                  soInvoiceTrackingTable.InvoicePDFUrl = soInvoicepath; 
      
                  soInvoiceTrackingTable.Processed = NoYes::Yes; 
      
                  ttsbegin; 
      
                  salesInvoiceTrackingTable.write(); 
      
                  ttscommit; 
      
              } 
      
       
      
                                       
      
               
      
          } 
      
       
      
          public static str buildConnectionToAzure(System.IO.Stream _stream, Filename _filename) 
      
          { 
      
              str connectionString = 'DefaultEndpointsProtocol=https;AccountName=abcdefghijklmn;AccountKey=njnkknjhxTphD2dTXvfk+AStgbkg2A==;EndpointSuffix=core.windows.net'; 
      
              str containerName = 'salesinv'; 
      
       
      
              BlobStorage.CloudStorageAccount storage = BlobStorage.CloudStorageAccount::Parse(connectionString); 
      
              BlobStorage.Blob.CloudBlobClient cloudClient = storage.CreateCloudBlobClient(); 
      
              BlobStorage.Blob.CloudBlobContainer cloudCon = cloudClient.GetContainerReference(containerName); 
      
              cloudCon.CreateIfNotExists(null,null); 
      
       
      
              BlobStorage.Blob.CloudBlockBlob blockBlob = cloudCon.GetBlockBlobReference(_fileName); 
      
       
      
              if(blockBlob && !blockBlob.Exists(null,null)) 
      
              { 
      
                  BlobStorage.Blob.BlobProperties blobProperties = blockBlob.Properties; 
      
                  blobProperties.ContentType = 'application/pdf'; 
      
                  blockBlob.UploadFromStreamAsync(_stream).Wait(); 
      
                  blockBlob.FetchAttributes(null,null,null); 
      
       
      
                  if(blobProperties.Length == _stream.Length) 
      
                  { 
      
                      return blockBlob.StorageUri.PrimaryUri.ToString(); 
      
                  } 
      
              } 
      
              return ''; 
      
                       
      
          } 
      
       
      
      } 

      Code Explanation: 

      Step 1: We created the runnable class to insert the SO invoice PDF to the azure blob storage container. Whenever we run the batch it inserts only one not processed (The SO invoices which are in the not processed status in the sales invoice tracking table) SO invoice to azure blob container. Declared all the necessary buffers. Some of the Dotnet C# classes has been used, below is the namespace for it. 

       
      using BlobStorage = Microsoft.WindowsAzure.Storage; 

      using Microsoft.WindowsAzure.Storage.Blob; 

      using Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService; 

      using Azure.Storage.Blobs; 

      
      public static void main(Args _args) 
      
      { 
      
          SCCSalesInvoiceTrackingTable salesInvoiceTrackingTable; 
      
          CustInvoiceJour custInvoiceJour; 
      
       
      
      //checking whether SO invoice is stored in the azure container or not with help of salesInvoiceTrackingTable table. 
      
       
      
          select firstonly custInvoiceJour 
      
              join salesInvoiceTrackingTable 
      
              where custInvoiceJour.RecId == salesInvoiceTrackingTable.CustInvoiceJourRecId 
      
              && salesInvoiceTrackingTable.Processed == NoYes::No; 
      
               
      
          if(custInvoiceJour.RecId) 
      
          { 
      
              SCCSalesInvoiceTrackingTable soInvoiceTrackingTable; 
      
              Args                        args; 
      
              SalesInvoiceController      controller = SalesInvoiceController::construct(); 
      
              SalesInvoiceContract        contract = new SalesInvoiceContract(); 
      
              SRSReportExecutionInfo      executionInfo = new SRSReportExecutionInfo(); 
      
              SRSPrintDestinationSettings settings; 
      
              Map                         reportParametersMap; 
      
              SRSReportRunService         srsReportRunService = new SrsReportRunService(); 
      
              SRSProxy                    srsProxy; 
      
              System.Byte[]               reportBytes = new System.Byte[0](); 
      
              ParameterValue[]            parameterValueArray; 
      
              System.IO.Stream            stream; 
      
       
      
       
          

      Step2: Creating the SO invoices related Contract and Controller classes for the SO invoices details. Setting the print destination for report. 

      
      //Constructing sales order invoices related contract and controller class and passing the sales order invoices details 
      
       
      
              args = new Args(); 
      
              args.record(custInvoiceJour); 
      
              contract.parmRecordId(custInvoiceJour.RecId); 
      
              controller.parmArgs(args); 
      
              controller.parmReportName(ssrsReportStr(SalesInvoice, Report)); 
      
              controller.parmShowDialog(false); 
      
              controller.parmLoadFromSysLastValue(false); 
      
              controller.parmReportContract().parmRdpContract(contract); 
      
              controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration()); 
      
              controller.parmReportContract().parmReportExecutionInfo(executionInfo); 
      
       
      
      // setting the print destination for report 
      
              settings = controller.parmReportContract().parmPrintSettings(); 
      
              settings.printMediumType(SRSPrintMediumType::File); 
      
              settings.fileFormat(SRSReportFileFormat::PDF); 
      
       
          

      Step3: Using the SRSReportRunService to run the sales order invoice and passing the SO invoices details to it and converting the SO invoice contract details into Map. Using the map data converting into array.  

      
      //Using the SRSReportRunService to run the sales order invoice and passing the SO invoices details to it.    srsReportRunService.getReportDataContract(controller.parmreportcontract().parmReportName()); 
      
              srsReportRunService.preRunReport(controller.parmreportcontract()); 
      
       
      
      //Converting the SO invoice contract into Map 
      
              reportParametersMap = srsReportRunService.createParamMapFromContract(controller.parmReportContract()); 
      
               
      
      // Converting SO invoice contract Map to array       
      
             parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap); 
      
       
      
       
          

      Step4:  Converting the SO invoice report configuration into SSRX proxy. Using SSRSProxy converted into byte array by pasing report path, filename and device info. 

      
      //Converting the SO invoice report configuration into SSRX proxy. 
      
              srsProxy = SRSProxy::constructWithConfiguration(controller.parmReportContract().parmReportServerConfig()); 
      
       
      
              // Actual rendering to byte array using So invoices contract parameters array and passing the file format and device info. 
      
              reportBytes = srsproxy.renderReportToByteArray(controller.parmreportcontract().parmreportpath(), parameterValueArray, settings.fileFormat(),settings.deviceinfo());         
      
       
      
      //Using report byte array converting into Stream  
      
              if (reportBytes) 
      
              { 
      
                  stream = new System.IO.MemoryStream(reportBytes); 
      
              } 
      
       
      
      //Constructing the file name for the file we need to store in the container 
      
              Filename fileName = strFmt('SalesInvoiceReport_%1_%2',custInvoiceJour.SalesId,custInvoiceJour.InvoiceId); 
      
       
          

      Step5: Building a connection to the Azure blob storage container and inserting the file stream into the container. Return back the file path. 

      
      public static str buildConnectionToAzure(System.IO.Stream _stream, Filename _filename) 
      
       { 
      
           str connectionString = 'DefaultEndpointsProtocol=https;AccountName=sainacloud;AccountKey=odn2kjeknkwnn xnjwjkmjjnxdn+AStSUjDVg==;EndpointSuffix=core.windows.net'; 
      
           str containerName = 'sccsalesinvoice'; 
      
               
      
      // using azure blob storage container connection string and container name building a connection to the container. 
      
           BlobStorage.CloudStorageAccount storage = BlobStorage.CloudStorageAccount::Parse(connectionString); 
      
           BlobStorage.Blob.CloudBlobClient cloudClient = storage.CreateCloudBlobClient(); 
      
           BlobStorage.Blob.CloudBlobContainer cloudCon = cloudClient.GetContainerReference(containerName); 
      
       
      
      // Checking with the provided container name is present or not. If not present it then we are creating new container with provided container name. 
      
           cloudCon.CreateIfNotExists(null,null); 
      
       
      
      //getting a reference for a blob with provided name 
      
           BlobStorage.Blob.CloudBlockBlob blockBlob = cloudCon.GetBlockBlobReference(_fileName); 
      
       
      
           if(blockBlob && !blockBlob.Exists(null,null)) 
      
           { 
      
      // providing the blob properties and inserting the SO invoice converted stream into the container and return back the blob(file) path. 
      
               BlobStorage.Blob.BlobProperties blobProperties = blockBlob.Properties; 
      
               blobProperties.ContentType = 'application/pdf'; 
      
               blockBlob.UploadFromStreamAsync(_stream).Wait(); 
      
               blockBlob.FetchAttributes(null,null,null); 
      
       
      
               if(blobProperties.Length == _stream.Length) 
      
               { 
      
                   return blockBlob.StorageUri.PrimaryUri.ToString(); 
      
               } 
      
           } 
      
           return ''; 
      
                       
      
       } 
          

      Step 6: Once the file inserted into the Azure container and the inserting that file path to the sales invoice tracking table. 

      
      // Building a connection to azure blob container and uploading the file and getting the file path  
      
              str soInvoicepath = SCCSalesInvoiceToBlob::buildConnectionToAzure(stream, filename); 
      
       
      
              select soInvoiceTrackingTable 
      
                  where soInvoiceTrackingTable.CustInvoiceJourRecId == custInvoiceJour.RecId; 
      
       
      
              if(soInvoiceTrackingTable.RecId) 
      
              { 
      
                  soInvoiceTrackingTable.selectForUpdate(true); 
      
              } 
      
      // Updating the file path for SO invoice in the tracking table 
      
              soInvoiceTrackingTable.CustInvoiceJourRecId = custInvoiceJour.RecId; 
      
              soInvoiceTrackingTable.SalesId = custInvoiceJour.SalesId; 
      
              soInvoiceTrackingTable.InvoiceId = custInvoiceJour.InvoiceId; 
      
              soInvoiceTrackingTable.InvoicePDFUrl = soInvoicepath; 
      
              soInvoiceTrackingTable.Processed = NoYes::Yes; 
      
              ttsbegin; 
      
              soInvoiceTrackingTable.write(); 
      
              ttscommit; 
      
          }                         
      
               
      
      } 
          

      Output 

        Run the batch job using runnable batch job URL. Once the execution is completed, it will insert the SO invoice PDF into azure container and the file path will be inserted into SCCSalesInvoiceTrackingTable. 

        The file is successfully inserted into the azure blob container. 

        The file path will be inserted in the SO invoice tracking table. 

        Using the file URL we can view the SO invoice PDF. 

        Note: If the container is public then only you can access the file with URL. If container is private you can access with SAS token.