Appearance
Report Generation
GrydReports supports three generation modes: synchronous (on-demand), asynchronous (background), and batch (multiple reports). All modes follow the same pipeline: validate → fetch data → render → store → deliver.
Generation Pipeline
Request → Validate Parameters → Fetch Data → Render → Store File → Deliver (optional)
│ │ │ │ │
IReportFilter IReportDataSource IReportRenderer IReportFileStore IReportDelivery
(OnBefore...) (FetchDataAsync) (RenderAsync) (SaveAsync) (DeliverAsync)Synchronous Generation
Returns the generated file immediately. Best for small-to-medium reports:
Via API
bash
POST /api/v1/reports/generate
Content-Type: application/json
{
"templateId": "monthly-sales",
"format": 1,
"parameters": {
"month": 1,
"year": 2026
},
"requestedBy": "user@company.com"
}Response 201 Created:
json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"templateId": "monthly-sales",
"reportName": "Sales Report - January 2026",
"format": 1,
"status": 3,
"fileName": "monthly-sales-2026-01.pdf",
"fileSizeBytes": 245760,
"downloadUrl": "/api/v1/reports/f47ac10b-.../download",
"generationDuration": "00:00:02.341",
"createdAt": "2026-02-19T10:30:00Z"
}Via MediatR
csharp
var result = await mediator.Send(new GenerateReportCommand(
TemplateId: "monthly-sales",
Format: ReportFormat.Pdf,
Parameters: new Dictionary<string, object?>
{
["month"] = 1,
["year"] = 2026
},
RequestedBy: "user@company.com"
));
if (result.IsSuccess)
{
var report = result.Data!;
Console.WriteLine($"Generated: {report.FileName} ({report.FileSizeBytes} bytes)");
}
else
{
Console.WriteLine($"Error: {result.ErrorMessage}");
}Asynchronous Generation
Queues the report for background processing. Returns immediately with a tracking ID. Ideal for large reports or when delivery is configured:
Via API
bash
POST /api/v1/reports/generate-async
Content-Type: application/json
{
"templateId": "annual-financial",
"format": 2,
"parameters": {
"year": 2025
},
"delivery": {
"method": 2,
"recipients": ["cfo@company.com", "finance@company.com"],
"emailSubject": "Annual Financial Report 2025",
"emailBody": "Please find the attached annual financial report."
},
"requestedBy": "admin@company.com"
}Response 202 Accepted:
json
{
"id": "a1b2c3d4-...",
"templateId": "annual-financial",
"status": 1,
"createdAt": "2026-02-19T10:35:00Z"
}Via MediatR
csharp
var result = await mediator.Send(new GenerateReportAsyncCommand(
TemplateId: "annual-financial",
Format: ReportFormat.Excel,
Parameters: new Dictionary<string, object?> { ["year"] = 2025 },
Delivery: new DeliveryOptions
{
Method = DeliveryMethod.Email,
Recipients = ["cfo@company.com"]
},
RequestedBy: "admin@company.com"
));
// Returns immediately — report is generated in background
var trackingId = result.Data!.Id;Polling for Status
bash
GET /api/v1/reports/{trackingId}Status transitions: Queued (1) → Generating (2) → Completed (3) or Failed (4) → Delivered (5)
IReportGenerator
The central orchestrator that wires everything together:
csharp
public interface IReportGenerator
{
// Generate and return the output stream
Task<Result<ReportOutput>> GenerateAsync<TData, TParams>(
string templateId, TParams parameters, ReportFormat format,
CancellationToken ct = default);
// Generate + store in file storage + persist execution metadata
Task<Result<ReportResultDto>> GenerateAndStoreAsync<TData, TParams>(
string templateId, TParams parameters, ReportFormat format,
CancellationToken ct = default);
// Generate + store + deliver via configured channel
Task<Result<ReportResultDto>> GenerateAndDeliverAsync<TData, TParams>(
string templateId, TParams parameters, ReportFormat format,
DeliveryOptions delivery, CancellationToken ct = default);
// List all registered report templates
IReadOnlyList<ReportDefinitionDto> GetAvailableReports();
}Download
Retrieve a previously generated report file:
bash
GET /api/v1/reports/{id}/downloadReturns a FileStreamResult with proper Content-Type and Content-Disposition headers.
Execution History
Query past report executions with filters:
bash
GET /api/v1/reports/history?templateId=monthly-sales&status=3&format=1&pageNumber=1&pageSize=20Query Parameters:
| Parameter | Type | Description |
|---|---|---|
templateId | string? | Filter by template |
status | int? | Filter by status (1=Queued, 2=Generating, 3=Completed, 4=Failed, 5=Delivered, 6=Cancelled) |
format | int? | Filter by format (1=PDF, 2=Excel, 3=CSV, 4=HTML) |
fromDate | DateTime? | Start of date range |
toDate | DateTime? | End of date range |
pageNumber | int | Page number (default: 1) |
pageSize | int | Page size (default: 20) |
Delete
Soft-delete a report execution and its associated file:
bash
DELETE /api/v1/reports/{id}Returns 204 No Content on success.
Size Limits
Configure automatic async fallback for large reports:
csharp
options.MaxSyncGenerationSize = 50 * 1024 * 1024; // 50 MB max for sync
options.ForceAsyncAboveBytes = 10 * 1024 * 1024; // Force async above 10 MB
options.SyncGenerationTimeout = TimeSpan.FromMinutes(2);ReportOutput
The output object returned from generation:
csharp
public sealed class ReportOutput : IDisposable
{
public Stream Content { get; init; } // File content stream
public string FileName { get; init; } // e.g. "report.pdf"
public string ContentType { get; init; } // e.g. "application/pdf"
public ReportFormat Format { get; init; } // Pdf, Excel, Csv, Html
public long FileSizeBytes { get; init; } // Size in bytes
public ReportMetadata Metadata { get; init; } // Title, Author, etc.
public TimeSpan GenerationDuration { get; init; }
public void Dispose() => Content?.Dispose();
}Resource Management
ReportOutput implements IDisposable. Always dispose when using the output directly:
csharp
using var output = (await generator.GenerateAsync<SalesData, SalesParams>(
"sales", parameters, ReportFormat.Pdf)).Data!;
// Use output.Content stream...