Reports
Genie offers three distinct kinds of report, for three different needs:
- Code-defined reports — a C# class that loads data, formats it, and returns a downloadable file (typically a PDF). Use these for pixel-formatted, printable documents (invoices, statements).
- SQL-view report grids — an ordinary read-only
*.view.xmlover a SQL view. Use these for interactive, filterable, on-screen reporting tables. - Report pages — a
*.report.xmldashboard of tiles, charts and tables over several datasets. Use these when one number or one grid is not the answer. Fully documented under Report pages (built by hand or in the designer).
Code-defined reports
Section titled “Code-defined reports”A report is a class deriving from ReportBase (implementing IReportBuilder). It has full access
to dependency injection (DbContext, session parameters, configuration) and owns its own data loading,
formatting and styling. It implements BuildAsync() to return an IReport (content bytes + file name +
content type + size).
The report key is the attribute’s positional argument; DisplayName, Description and Category
are optional named properties (all three are inferred from the key when omitted).
[Report("sales.invoice", DisplayName = "Invoice", Category = "Sales")]public class InvoiceReport( ILogger logger, ISessionParametersService sessionParams, IServiceProvider services, AppDbContext db) : ReportBase(logger, sessionParams, services){ public override async Task<IReport> BuildAsync() { var invoiceId = GetParameter<long>("invoiceId"); // load data, render a PDF, return an IReport … return new Report { Content = bytes, ContentType = "application/pdf", FileName = "invoice.pdf" }; }}A report is resolved from the request scope, so it can inject anything the rest of the app injects —
the request’s DbContext, unit of work and session services included.
The engine has no PDF dependency of its own: IReport is just content + content type + file name, so the
choice of writer is yours. The Inventory sample uses QuestPDF (whose
Community licence is free below a revenue threshold) — see
sample/Inventory/api/Reports/StockSummaryReport.cs.
Registration
Section titled “Registration”Reports are discovered by reflection over the [Report] attribute. Discovery registers both halves
that a working report needs — the builder type in the service container (scoped) and the key → type entry
in the IReportRegistry — so there is nothing else to wire:
services.AddGenie<AppDbContext>(genie => genie .LoadFromConfiguration(configuration) .AddReports(typeof(Program).Assembly));AddReports(...) is optional: without it the engine scans the entry assembly plus Genie.Engine
itself, which covers the normal single-host layout. Call it when the reports live in a separate class
library, or to be explicit.
At request time ReportService looks the key up in the registry, resolves the builder from the current
request scope, passes the parameters via WithParameters(...), and calls BuildAsync().
The report endpoints sit at the root /report path — a sibling of /files, not under the versioned
/api/v1/genie base.
| Method | Route | Returns |
|---|---|---|
GET |
/report |
the registered reports: Key, DisplayName, Description, Category (metadata only) |
GET |
/report/{reportName} |
the generated file (FileResult) |
Every query-string value is passed through as a report parameter, except the reserved keys the endpoint
and viewer own — reportName, report, title, inline, download. Read parameters in the builder
with the typed helper GetParameter<T>("key").
GET /report/sales.invoice?invoiceId=42GET /report/sales.invoice?invoiceId=42&inline=trueinline=true marks the response Content-Disposition: inline so a browser previews it instead of
saving it; the default is an attachment (download). An unknown key is a 404 whose message deliberately
does not enumerate the other registered reports.
A report is authenticated but not verb-gated per report: any signed-in user who knows a key can generate it. Scope the data inside the builder (the sample reads the tenant from the session, never from a parameter).
Viewing a report in the UI
Section titled “Viewing a report in the UI”The endpoint is [Authorize], and the SPA holds its JWT in JavaScript — so a plain <a href> or
window.open to /report/{key} cannot send the Authorization header and gets a 401. Link instead to
the UI’s document viewer page, which fetches the bytes with the bearer token and renders them:
/report-view?report=sales.invoice&title=Invoice&invoiceId=42report names the report, title sets the heading, and everything else is forwarded to the builder as a
report parameter. The viewer renders a PDF as one scrolling column of every page, with a pager (its arrows jump to a
page; its counter follows the scroll), zoom, Download and Print; an
image renders inline; anything else downloads immediately.
Stored files have the same page at /file-view?path=Attachments/…&title=…, which previews images
only — every other type (PDFs included) downloads.
Both are ordinary SPA routes, so they can be reached from anywhere a route can:
<!-- A grid/record row action: navigates to the viewer page --><Action Name="Invoice" Icon="fa fa-file-pdf" Url="/report-view?report=sales.invoice&title=Invoice&invoiceId={Id}" />
<!-- Target="Modal" opens the same document in a dialog, without leaving the grid --><Action Name="Preview" Icon="fa fa-eye" Target="Modal" Url="/report-view?report=sales.invoice&title=Invoice&invoiceId={Id}" />
<!-- A navbar entry (no row context, so no {Id}) --><Item Name="stock-summary" Label="Stock Summary" Icon="fa fa-file-pdf" Route="/report-view?report=inventory.stock-summary&title=Stock Summary" />{Column} tokens in a row action’s Url are substituted from the row; tokens that land in the query
string are percent-encoded, so a value containing & or # can’t corrupt the parameters after it.
Host apps can also open a document directly, with <GenieDocumentViewer> (a page) or
<GenieDocumentModal> (a dialog), both exported from @orbyn-technologies/genie-engine-ui.
SQL-view report grids
Section titled “SQL-view report grids”Many “reports” need no code at all — they are just a read-only grid over a database view. Author a
normal object view whose <Sql> selects from a SQL view, and turn off CRUD:
<Table Name="LowStockReport" Slug="low-stock-report" Label="Low Stock" DisableActions="true" DisableControls="true"> <ParentView Name="catalog" /> <Sql PrimaryKey="ProductId" SortBy="Shortfall" SortDirection="DESC" PageSize="25"> <![CDATA[ SELECT v.ProductId, v.Sku, v.Name, v.OnHand, v.ReorderLevel, v.Shortfall FROM [Inventory].[vw_LowStock] v WHERE v.CompanyId = @SessionCompanyId ]]> </Sql> <Columns> <Column Name="Shortfall" Label="Shortfall" StyleClassesExpression="If(Shortfall > 10, 'badge bg-danger', 'badge bg-warning')" /> </Columns></Table>DisableActions/DisableControls make it a pure report — no create/edit/delete. Rows can still be
opened read-only; add ViewAction="Disabled" to drop that too. It still gets
paging, sorting, per-column search, lookups and expression-driven styling from the standard
object pipeline, and it is permission-filtered and tenant-scoped
like any other view. The same SQL view can also back a navbar
counts badge.
See sample/Inventory/models/views/LowStockReport.view.xml and CategoryStockReport.view.xml.
Report pages
Section titled “Report pages”A report page is a whole dashboard in one document — <Filters>, <DataSets>,
<Widgets> and <Layout> — served at /reports/{slug}. It can embed a report grid like the one above
read-only, so a dashboard summarizes the grids you already have rather than restating their SQL.
Unlike a view, a report definition is managed rather than migrated: it lives in the database and is imported through the Reports definitions grid, exactly like a workflow or a wizard — or authored in the visual designer, where editing writes to a draft and a separate Publish is what visitors finally see.
See sample/Inventory/models/reports/InventoryOverview.report.xml.
Which to use
Section titled “Which to use”| Need | Use |
|---|---|
| Printable/downloadable document, precise layout | Code-defined report (ReportBase + PDF) |
| Interactive on-screen table, filter/sort/search | SQL-view report grid (*.view.xml) |
| Several figures side by side — KPIs, charts, a top-N list | Report page (*.report.xml) |