VOOZH about

URL: https://dev.to/sapotacorp/ssrs-reports-in-fo-the-rdp-contract-controller-pattern-1nl2

⇱ SSRS reports in F&O: the RDP + Contract + Controller pattern - DEV Community


F&O has three reporting stories. Electronic Reporting (GER) is Microsoft's long-term direction for jurisdiction-specific statutory formats. Power BI covers analytical dashboards and workspace tiles. But for bespoke transactional documents - invoices, packing slips, remittance advice, customer statements - SSRS remains the workhorse. Teams new to F&O run into the "four-class pattern" and ask why it needs to be four classes.

Each class earns its place.

The four classes

For a single SSRS report, you build:

  1. RDP class (Report Data Provider) - runs the query, shapes the data, populates a temporary table
  2. Contract class - defines the input parameters the report accepts
  3. Controller class - orchestrates: reads the contract, invokes the RDP, opens the SSRS designer output
  4. UIBuilder class (optional) - customizes the parameter dialog

Skip any of them and you trade flexibility for short-term convenience:

  • Skip the Contract → can't batch the report from a scheduled job
  • Skip the Controller → can't call the report from a menu item without the dialog
  • Skip the UIBuilder → stuck with auto-generated dialogs that can't validate cross-field constraints

The pattern looks verbose until you need one of the things it gives you for free.

A worked example: customer statement

The job: print a customer statement, filtered by customer account and date range, with the option to include or exclude closed transactions.

Data contract

[DataContractAttribute]
public class MyCustStatementContract
{
 CustAccount custAccount;
 FromDate fromDate;
 ToDate toDate;
 boolean includeClosed;

 [DataMemberAttribute("CustAccount")]
 public CustAccount parmCustAccount(CustAccount _val = custAccount)
 { custAccount = _val; return custAccount; }

 [DataMemberAttribute("FromDate")]
 public FromDate parmFromDate(FromDate _val = fromDate)
 { fromDate = _val; return fromDate; }
 // ... toDate, includeClosed parm methods
}

The DataMember attributes let the framework bind dialog controls and serialize the contract for batch jobs. Serialization is what makes batching possible - the batch framework needs to recreate the parameter state after the job restarts on a different AOS.

Report Data Provider

[SRSReportParameterAttribute(classStr(MyCustStatementContract))]
public class MyCustStatementDP extends SRSReportDataProviderBase
{
 MyCustStatementTmp tmpTable;

 [SRSReportDataSetAttribute(tableStr(MyCustStatementTmp))]
 public MyCustStatementTmp getMyCustStatementTmp() { return tmpTable; }

 public void processReport()
 {
 MyCustStatementContract contract = this.parmDataContract() as MyCustStatementContract;
 CustTable cust;
 CustTrans trans;

 select cust where cust.AccountNum == contract.parmCustAccount();
 while select trans
 where trans.AccountNum == cust.AccountNum
 && trans.TransDate >= contract.parmFromDate()
 && trans.TransDate <= contract.parmToDate()
 && (contract.parmIncludeClosed() || !trans.Closed)
 {
 tmpTable.AccountNum = trans.AccountNum;
 tmpTable.Voucher = trans.Voucher;
 tmpTable.TransDate = trans.TransDate;
 tmpTable.Amount = trans.AmountCur;
 tmpTable.insert();
 }
 }
}

The temporary table MyCustStatementTmp is a regular AOT table - this is the dataset the .rdl file binds to in the visual designer.

Controller

public class MyCustStatementController extends SrsReportRunController
{
 public static void main(Args _args)
 {
 MyCustStatementController controller = new MyCustStatementController();
 controller.parmReportName(ssrsReportStr(MyCustStatement, Report));
 controller.parmArgs(_args);
 controller.startOperation();
 }
}

The controller stays tiny because the framework does the heavy lifting. startOperation() opens the dialog (generated from the contract unless a UIBuilder customizes it), runs the RDP, renders the SSRS report, handles output destinations.

When the UIBuilder earns its place

Use a UIBuilder when:

  • A dynamic control depends on contract state (e.g., a lookup that only populates after another field is set)
  • Cross-field validation before submission is required
  • Fields need to show or hide based on other fields' values

Auto-generated dialogs handle none of those. For simple contracts without cross-field rules, the UIBuilder is overkill and you skip it.

Deployment discipline

Once the four classes and the .rdl exist:

  1. Build the model - F&O compiles X++ and the report definition together.
  2. Deploy the report - via the F&O platform tool ReportDeployment.exe or Visual Studio's "Deploy Reports" context menu on the SSRS project.
  3. Restart SSRS service if the report already existed - otherwise cached definitions linger and your changes don't appear.
  4. Link the controller to a menu item via the AOT (MenuItem > Output) so users can launch it.
  5. Test batched - any controller inheriting SrsReportRunController can run as a batch. Set "Batch processing = Yes" in the dialog and confirm output lands in the Print Archive.

When ER or Power BI fits better

  • Electronic Reporting (GER) is the answer for statutory formats that change across jurisdictions (tax filings, e-invoicing). The data model + mapping + format design model is easier to version than SSRS .rdl files when the format mutates.
  • Power BI is the answer for dashboards, KPI tiles, analytical workspaces. Not for pixel-perfect transactional output.
  • SSRS still fits transactional documents with custom X++ data preparation. The default choice for "print this voucher" in 2026.

Hand-over package

A completed SSRS report ships with: four classes, .rdl file, menu item, AOT project file, batch-run test case, and deployment runbook. The next developer - internal or external - can rebuild and redeploy without archaeology. The pattern is verbose precisely so hand-over is fast.