Skip to main content

Creating a report

:::info Status Outline — expand with a full worked example (e.g. adding a new parameterized report with a grouping/aggregate column). :::

:::tip Use the shipped skill Prefer the create-report skill (in your scaffolded repo's .claude/skills/) over hand-rolling this. Adding or changing a CSV/PDF export on the result is a separate skill, create-print-form. :::

A Report is a parameterized, read-only query — no posting, no persistence of its own. It's also the one entity kind with a fully generated Blazor UI end to end (ReportUiEmitter) — there are no hand-written report pages left in the reference configuration.

The pieces

*Base/[KandraReportEntity], *Dto/[KandraReportForm], a filter/parameters shape, and a *Validator. Reports don't have a *Behavior in the same sense as Dictionaries/Documents — the query logic lives in the report's own execution service instead.

Shared filter shape

Use DateRangeDto for any From/To date-range parameter rather than ad-hoc separate properties — it's the shared convention across every report and rippled into Document list pages too.

Grouping and aggregates

[Aggregate]/AggregateKind/ICustomAggregate drive grouped-column totals in the generated MudDataGrid. ReportGroupingState carries the current grouping across renders.

Gotchas worth knowing before you start

  • IsCollectionOfGuid field discovery previously missed IReadOnlyList<Guid> (fixed, but worth knowing the discovery logic's shape if a collection field doesn't render as expected).
  • A nullable-reference type's ToDisplayString() includes a trailing ? — strip it if you're building a custom display formatter.
  • ReportPageBase needs a DataProcessorPageBase-style null-Filters render guard if your report's filter object can be null before first load.

See also