惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
爱范儿
爱范儿
月光博客
月光博客
The GitHub Blog
The GitHub Blog
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog
T
Tailwind CSS Blog
美团技术团队
D
Docker
V
Visual Studio Blog
Martin Fowler
Martin Fowler
博客园 - 聂微东
The Cloudflare Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
SSRS reports in F&O: the RDP + Contract + Controller pattern
SapotaCorp · 2026-05-24 · via DEV Community
Cover image for SSRS reports in F&O: the RDP + Contract + Controller pattern

SapotaCorp

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.