Understanding Report Controller Classes in Dynamics AX 2012
*Please note that the following assumes the reader understands the Dynamics AX 2012 concept of using a data contract class for report parameter and a data provider class for retrieving or preparing data for a Dynamics AX 2012 report. If the reader is unfamiliar with this concept, please examine the following document: AX 2012 Report Programming model.
Report controller classes in Microsoft Dynamics AX 2012 were designed using the Model View Controller (MVC) concept. This concept divides a given software application or process into three interconnected parts, so as to separate internal representations of information from the ways that information is presented to or accepted from the user. More information on this pattern can be found in this Wikipedia article on MVC.
The Microsoft implementation of this concept states that custom report controller classes inherit from the X++ based class named SRSReportRunController. This is the framework class for running reports using the MVC pattern. For more information on this class, please see the following on MSDN: http://msdn.microsoft.com/en-us/library/srsreportruncontroller.aspx
The report controller class contains methods that allow you to work with the data objects at different stages of the process. The prePromptModifyContract() method in the controller class is the designated location to place the code for modifying queries, before they are displayed in the dialog. So, any caller-based modification, locking of ranges based on the caller or the addition of other data sources, can be done within this method. The preRunModifyContract() method can be used to do this as well, but this method is invoked after the report dialog has been displayed. This means that the user never gets an option to modify or see the changes made to the query.
The next portion of this article is going to walk the code path of a standard report in Dynamics AX 2012 and show what the ‘bits and pieces’ do when the controller is called, to help see what really happens during the use of the controller class. The report that will be examined is the Inventory Value Report. In Dynamics AX 2012, the navigation is Inventory Management >> Reports >> Status >> Inventory value >> Inventory value.
Upon examination we find that the report uses a controller class named InventValueReportController. This class extends the SRSReportRunController class, inheriting the methods from the parent class or classes that are needed to implement the MVC pattern.
The main method is entry point for this class, and looks like the following (Dynamics AX 2012 R3 is being used for this example):
public static void main(Args _args) { InventValueReportController controller = InventValueReportController::construct(); InventValueReportContract inventValueReportContract = InventValueReportContract::construct(); controller.parmReportName(inventValueReportContract.parmReportName()); controller.parmDialogCaption("@SYS323601"); controller.startOperation(); }
At first glance, this looks pretty simplistic. The X++ code simply defines a variable named controller that contains an instance of the controller class, the data contract class is also defined and instantiated, the controller class is told the name of the report to run, a caption is provided for the dialog box that prompts the user for information, and the startOperation() method is called to run everything. In actuality, when the code is traced, this gets quite complex.
startOperation() calls the SRSReportRunController class method (as it is not overwritten in the current controller class), and runs the following code:
public SysOperationStartResult startOperation() { // get the report contract. This will initialize it as well. this.parmReportContract(); // call hook to change the contract before prompt is called. this.prePromptModifyContract(); // maintain a unique id for each report run execution instance. reportRunId = newGuid(); return super(); }
The report contract object is initialized, and the prePromptModifyContract method is called. This method is used to modify the report data contract PRIOR to any dialog being displayed to the end user. A unique Id is created for this report session, and the super() call is done.
The super() method call actually calls back into a class called SysOperationsController. The method being run looks like this:
public SysOperationStartResult startOperation() { if (this.prompt()) this.run(); return startResult; }
If the user clicks the OK button on the prompt, the run method is called. The run() method is the code that exists in the InventValueReportController class:
public void run() { InventValueReportContract inventValueReportContract = this.parmReportContract().parmRdpContract() as InventValueReportContract; InventValueReportInit inventValueReportInit = InventValueReportInit::construct(); inventValueReportContract.parmTransactionId(appl.curTransactionId(true)); inventValueReportContract.parmQuery(this.parmReportContract().parmQueryContracts().lookup(this.getFirstQueryContractKey())); InventValueReportController::setReportLayoutParameters(inventValueReportContract); if (this.isInBatch()) { InventValueReportContract.parmSRSPrintSettings(this.parmReportContract().parmPrintSettings().pack()); } this.saveLast(); inventValueReportInit.parmInBatch(this.isInBatch()); inventValueReportInit.parmCurrentBatch(this.parmCurrentBatch()); inventValueReportInit.parmInventValueReportContract(inventValueReportContract); inventValueReportInit.createTasks(); }
This method initializes a few other classes that are specific to this report, and starts the processing using the createTasks() method call of the initValueReportInit class (variable declared and instantiated at the beginning of this method).
The createTasks() method does a LOT of processing that is specific to this report. The only line I really want to point out is the following:
inventValueReportPrint.run();
This calls the run() method from another instantiated class called InventValueReportPrint. This method takes the objects that have been created, and uses this information to run the report:
public void run() { InventValueReportController controller = new InventValueReportController(); controller.parmReportName(inventValueReportContract.parmReportName()); controller.parmReportContract().parmReportCaption("@SYS323601"); controller.parmDialogCaption("@SYS323601"); if (conLen(inventValueReportContract.parmSRSPrintSettings()) > 0) { controller.parmReportContract().parmRdpContract(inventValueReportContract); controller.parmReportContract().parmPrintSettings(new SRSPrintDestinationSettings(inventValueReportContract.parmSRSPrintSettings())); } // Do not show the dialog in the report viewer controller.parmShowDialog(false); controller.runReport(); }
The last line actually starts the generation of the report using the prior information, the data contract class, and the data provider class to retrieve the information.
In summary, this article was intended to try to explain in a bit more detail what a Dynamics AX 2012 Report controller class really does, and give a small insight into some of the core objects that are used when a report is run.
Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.
Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.