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:
- Create a Controller class to orchestrate the report execution
- Override the Print Destination settings for the report
- 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(); }