How To: Displaying Reports using PDF Viewers

Microsoft Dynamics AX 2012

 

Let's face it, inconsistencies in pagination rules applied by the various SQL Server Reporting Services rendering engines can be a pain for customers.  Customers are not concerned with the differences in viewing an HTML document in a browser versus directing the file to PDF or Word.  MSDN offers a thorough overview of the pagination rules, controls, and limitations offered by SQL Server 2012 (technet.microsoft.com/en-us/library/dd255278.aspx).  Unfortunately, this article doesn't offer any meaningful solutions to address the issue.  Customers expect the page breaks to be consistent across mediums.  As a result, they simply view this as an annoying bug in the application.

 

SCENARIO:  Customer runs the Inventory Dimensions report which produces 30 pages when viewed on screen. This customer is viewing the report on screen first to get a preview of the document and really only wants to print the content displayed between pages 10-14. He now attempts to print the document using Ctrl+P and select pages 10-14 expecting the same records in the output. However, when the customer gets the printout they see that it contains a different section of the report. In fact, the same report when sent to printer would have produced 72 pages when sent to the printer.

Given the fact that this has been an issue in SSRS since version 2005, it's logical to assume that things aren't going to change in the platform. So, it's up to us as developers to be creative in finding a better solution.

 

What does the customer *REALLY* want?

In the scenario above, the customer is using the report to preview a document that will ultimately be sent to a printer.  The report is being displayed on the screen first to give the customer the opportunity to identify which pages they want directed to the printer.  With the understanding of varying pagination rules between hard and soft pagination engines, it's best to pursue a solution that most effectively addresses the customer need (e.g. Preview the document).

 

SOLUTION

Let's change the medium used to preview the document.  Instead of displaying the report to screen using the HTML viewer, let's display the report using a PDF viewer which enforces hard pagination rules that are consistent with the printed documents.  The customer will lose some of the interactive capabilities offered by reports viewed in a browser window.  However, the customer will be able to achieve their primary goals for running the report.

In this example, we'll use the Asset Balances report which comes in the out-of-box solution.  The solution described below can be broken down into the following actions:

  1. Create a Controller class to orchestrate the report execution
  2. Override the Print Destination settings for the report
  3. Launch the report in a PDF viewer upon completion

 

STEP #1) Introduce a Controller class

First thing you'll want to do is add the basic framework for a controller class to launch the report.  This simply involves the creation of a class that extends the SrsReportRunController system class.

 

Code Snippet:

public DemoAssetBalancesController extends SrsReportRunController 

    #define.reportName('AssetBalances.Report')
}

public static void main(Args _args)
{
    DemoAssetBalancesController controller = new DemoAssetBalancesController();

    // establish the report name
    controller.parmReportName(#reportName);

    // start the operation
    controller.startOperation();
}

 

STEP #2) Override Print Destination Settings

Now, you'll want to customize the report execution to utilize a hard pagination solution when viewing the report on the screen.  Do this by overriding the Print Destination settings for the report execution.

 

Code Snippet:

public DemoAssetBalancesController extends SrsReportRunController 

   FilePath reportFilePath;

    #define.fileName('AssetBalReport.pdf')
    #define.reportName('AssetBalances.Report')
}

public FilePath localFilePath(FilePath _reportFile = reportFilePath)

    reportFilePath = _reportFile;
    return reportFilePath;
}

public static void main(Args _args)

    DemoAssetBalancesController controller = new DemoAssetBalancesController(); 
    SRSPrintDestinationSettings printSettings; 
    FilePath reportFile;

    // prepare the client for local file operations
    new InteropPermission(InteropKind::ClrInterop).assert();

    // use temp folder on the client
    reportFile = System.IO.Path::Combine(WinAPI::getTempPath(), #fileName);

    // update the file path to the local file
    controller.localFilePath(reportFile);

    // establish the report name
    controller.parmReportName(#reportName);

    // set print medium and destination
    printSettings = controller.parmReportContract().parmPrintSettings();
    printSettings.printMediumType(SRSPrintMediumType::File);
    printSettings.fileFormat(SRSReportFileFormat::PDF);
    printSettings.overwriteFile(true);
    printSettings.fileName(reportFile);

    // start the operation
    controller.startOperation();
 }

 

STEP #3) Display the report using a PDF Viewer

The last step involves adding post-processing code to the controller class to display the report in a PDF Viewer.  Fortunately, the Reporting Framework offers simple extension points to make this possible.  I've broken this task down into two parts....

Begin by overriding the renderingComplete notification dispatched by the base class in your control object.  Here's the code:

Code Snippet:

public client static void renderingComplete(SrsReportRunController _sender, SrsRenderingCompletedEventArgs _eventArgs)
{
    #macrolib.WinAPI
    SRSReportExecutionInfo executionInfo = _eventArgs.parmReportExecutionInfo();
    DemoAssetBalancesController controller = _sender;

    // check to see if the action was successful
    if (executionInfo && executionInfo.parmIsSuccessful())
    {
        // check to see if the file exists
        if (WINAPI::fileExists(controller.localFilePath()))
        {
            WinAPI::shellExecute(controller.localFilePath(), '', '', #ShellExeOpen);
        }
    }
}

Finally, add an Event Handler to the preRunModifyContract method of the controller class.  Be sure to include a call to super() at the end of the function.

Code Snippet:

protected void preRunModifyContract()
{
    this.renderingCompleted += eventhandler (DemoAssetBalancesController::renderingComplete);

    super();
}

You're Done....!!!

This example can be applied to any production report whether it is provided out-of-the-box or introduced as part of an partner customization.  Be sure to update the Output Menu Item used to access the report.  It must point to the Controller class that you have introduced as part of this exercise.  Enjoy.!

Comments

  • Anonymous
    March 15, 2014
    Thanks! This is one of the common gaps in AX 2012 that is also used as a selling argument of some document management Add-ons :-)

  • Anonymous
    June 09, 2015
    Thanks for your article. I want print salespackingslip and modify destination through x++. How set parameter for record sales or cus journal ? The pdf is generate but is blank. static void main(Args _args) {    Args args = new args();    BS_PackingSlipController        controller = new BS_PackingSlipController();    SalesPackingSlipContract        contract = new SalesPackingSlipContract();    SRSPrintDestinationSettings     printSettings;    FilePath                        reportFile;    // added by fp    CustPackingSlipJour             custJour;      SalesId                         id;    //Define Invoice    id = "BSD-VBC0009184";    select firstOnly custJour where custJour.SalesId == id;    args = new Args();    args.record(custJour);    args.parmEnumType(enumNum(PrintCopyOriginal));      //Setting the enum Type.    args.parmEnum(PrintCopyOriginal::OriginalPrint);    //Setting the enum value for    // prepare the client for local file operations    new InteropPermission(InteropKind::ClrInterop).assert();    // use temp folder on the client    reportFile = System.IO.Path::Combine(WinAPI::getTempPath(), #fileName);    // update the file path to the local file    controller.localFilePath(reportFile);    // establish the report name    controller.parmReportName(#reportName);    // Assign args to controller      controller.parmArgs(args);    // Explicitly provide all required parameters    contract.parmRecordId(custJour.RecId);    controller.parmReportContract().parmRdpContract(contract);    // set print medium and destination    printSettings = controller.parmReportContract().parmPrintSettings();    printSettings.printMediumType(SRSPrintMediumType::File);    printSettings.fileFormat(SRSReportFileFormat::PDF);    printSettings.overwriteFile(true);    printSettings.fileName(reportFile);    // start the operation    controller.startOperation(); }