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.