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.!