I recently got a requirement to create a custom service that will input a set of parameters required to trigger a report (customer invoice and customer statements). When this report is output the goal is to have the destination be an Azure Fileshare.

In order to do this, I needed to set up a few helper classes.

First a data contract, this will hold the byte array for the report and a new doNotSaveFile flag.

[ExtensionOf(classStr(SrsReportDataContract))]
final public class SrsReportDataContract_Extension
{
    private System.Byte[] reportBytes;
    private boolean doNotSaveFile;

    public System.Byte[] parmreportBytes(System.Byte[] _reportBytes = reportBytes)
    {
        reportBytes = _reportBytes;
        return reportBytes;
    }

    public boolean parmdoNotSaveFile(boolean _doNotSaveFile = doNotSaveFile)
    {
        doNotSaveFile = _doNotSaveFile;
        return doNotSaveFile;
    }
}

The second helper class will do most of the work for us. This will subscribe to the toSendFile delegate. If a contract has our new flag specified it will save the report bytes back to the contract. Then it will set the result to false to stop the base code from saving the file. Additionally this helper class has a method to accept a report controller and return a byte array.

public static class SrsReportRunHelper
{
    [SubscribesTo(classStr(SRSPrintDestinationSettingsDelegates), delegateStr(SRSPrintDestinationSettingsDelegates, toSendFile))]

    public static void SRSPrintDestinationSettingsDelegates_toSendFile(System.Byte[] _reportBytes, SrsReportRunPrinter _printer, SrsReportDataContract _dataContract, Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[] _paramArray, EventHandlerResult _result)
    {
        if (_dataContract.parmDoNotSaveFile())
        {
            _dataContract.parmReportBytes(_reportBytes);
            _result.result(false);
        }
    }

    public static System.Byte[] printReportToByteArray(SrsReportRunController _controller)
    {
        SrsReportDataContract reportContract = _controller.parmReportContract();
        SRSPrintDestinationSettings printerSettings = reportContract.parmPrintSettings();
        printerSettings.printMediumType(SRSPrintMediumType::File);
        printerSettings.fileFormat(SRSReportFileFormat::PDF);

        reportContract.parmDoNotSaveFile(true);

        _controller.parmShowDialog(false);
        _controller.startOperation();

        return reportContract.parmReportBytes();
    }
}

Here is an example use case for the customer statement report being ran by a runnable class. The output of this report will be a pdf file on the corresponding Azure Fileshare.

using System.Net;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.File;

internal final class ReportToAzureFileShare
{

    public static void main(Args _args)
    {
        CustTable _custTable = CustTable::find("XXXXX");
        str _filename = "custStatement.pdf";

        CustAccountStatementExtController     controller = new CustAccountStatementExtController();
        SRSPrintDestinationSettings            printSettings;
        CustAccountStatementExtContract        Contract;

        controller.parmReportName(PrintMgmtDocType::construct(PrintMgmtDocumentType::CustAccountStatement).getDefaultReportFormat());
        controller.parmLoadFromSysLastValue(true);
        controller.parmShowDialog(false);
        printSettings = controller.parmReportContract().parmPrintSettings();

        printSettings.printMediumType(SRSPrintMediumType::File);
        printSettings.fileFormat(SRSReportFileFormat::PDF);
        printSettings.overwriteFile(true);
        printSettings.fileName(_filename);

        Contract = controller.parmReportContract().parmRdpContract() as CustAccountStatementExtContract;

        Contract.parmFromDate(str2Date("1/1/1999", 213));
        Contract.parmToDate(str2Date("12/1/2023", 213));
        Contract.parmCustAccount(_custTable.AccountNum);

        SrsReportDataContract reportContract = controller.parmReportContract();
        SRSPrintDestinationSettings printerSettings = reportContract.parmPrintSettings();
        printerSettings.printMediumType(SRSPrintMediumType::File);
        printerSettings.fileFormat(SRSReportFileFormat::PDF);

        reportContract.parmDoNotSaveFile(true);

        controller.parmShowDialog(false);
        controller.startOperation();
                
        System.Byte[] reportBytes = reportContract.parmReportBytes();

        //Sent to Azure
        TextStreamIo textIo;
        str storageAccountName = "XXXXX";
        str key = "XXXXX"; //Key value
        str folderName = "XXXXX";
        str fileName = 'XXXXX.pdf';
        var bytes = reportBytes;

        try
        {
            using (System.IO.FileStream extnFileStrm = System.IO.File::Create(fileName, System.IO.FileMode::OpenOrCreate))
            {
                var storageCredentials = new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(storageAccountName, key);
                CloudStorageAccount storageAccount = new Microsoft.WindowsAzure.Storage.CloudStorageAccount(storageCredentials, true);

                extnFileStrm.Seek(0, System.IO.SeekOrigin::End);
                extnFileStrm.Write(bytes, 0, bytes.Length);

                if (storageAccount)
                {
                    CloudFileClient fileClient = storageAccount.CreateCloudFileClient();
                    CloudFileShare fileShare = fileClient.GetShareReference("css-servicetest-share");

                    if (fileshare.Exists(null, null) == false)
                    {
                        throw Error("File share in the storage account not exists");
                    }

                    Microsoft.WindowsAzure.Storage.File.CloudFileDirectory cloudFileDir = fileShare.GetRootDirectoryReference();
                    container folders = str2con(folderName, '/');

                    int folder = 1;
                    while (folder <= conLen(folders))
                    {
                        str folderDirName = conPeek(folders, folder);

                        cloudFileDir = cloudFileDir.GetDirectoryReference(folderDirName);
                        cloudFileDir.CreateIfNotExists(null, null);

                        folder++;
                    }

                    Microsoft.WindowsAzure.Storage.File.CloudFile file = cloudFileDir.GetFileReference(filename);

                    if (extnFileStrm.CanSeek)
                    {
                        extnFileStrm.Seek(0, System.IO.SeekOrigin::Begin);
                    }

                    file.uploadFromStream(extnFileStrm, null, null, null);

                }
            }
        }
        catch(Exception::Error)
        {
            error("Operation cannot be completed");
        }

    }

}

Note that we set up our report objects like normal and start the operation on the controller. We do need to set our new flag on the data contract class to not save the file. Then we are able to obtain the results of the report as a byte array (bolded in code above).

After we have the byte array we are able to connect to our Azure storage account using a key and push the report to the location specified. We additionally have the option to set the report name as it is being saved in Azure.

Reference for report to byte array: https://community.dynamics.com/blogs/post/?postid=e39683ca-d8ce-4de4-b245-c2add84bd6f7

Keep reading about D365 tips and tricks here:  https://markedcode.com/index.php/category/d365/

Author

Write A Comment