Skip to content

Report Templates

Templates are the heart of GrydReports. They define the structure, metadata, and supported formats for each report type using a code-first approach.

IReportTemplate<TData>

Every template implements IReportTemplate<TData>:

csharp
public interface IReportTemplate<in TData>
{
    string TemplateId { get; }                              // Unique identifier
    string DisplayName { get; }                             // Human-readable name
    string? Description { get; }                            // Optional description
    IReadOnlyList<ReportFormat> SupportedFormats { get; }   // PDF, Excel, CSV, HTML
    IReadOnlyList<ReportParameterDefinition> Parameters { get; }  // Input params
    PageSettings PageSettings { get; }                      // Page layout (default A4)
    ReportMetadata GetMetadata(TData data);                 // Dynamic metadata
}

Basic Template

csharp
public class MonthlyBillingTemplate : IReportTemplate<BillingData>
{
    public string TemplateId => "monthly-billing";
    public string DisplayName => "Monthly Billing Report";
    public string? Description => "Customer billing summary by month";

    public IReadOnlyList<ReportFormat> SupportedFormats =>
        [ReportFormat.Pdf, ReportFormat.Excel, ReportFormat.Csv];

    public IReadOnlyList<ReportParameterDefinition> Parameters =>
    [
        new("month", "Month", "int", required: true),
        new("year", "Year", "int", required: true),
        new("customerId", "Customer ID", "guid", required: false)
    ];

    public ReportMetadata GetMetadata(BillingData data) => new()
    {
        Title = $"Billing - {data.MonthName} {data.Year}",
        Author = "Finance Department",
        Subject = "Monthly Billing Summary",
        Keywords = "billing, finance, monthly"
    };
}

Parameters

Define typed input parameters with validation and defaults:

csharp
public IReadOnlyList<ReportParameterDefinition> Parameters =>
[
    // Required string parameter
    new("customerId", "Customer", "guid", required: true),

    // Optional with default
    new("format", "Output Format", "string", required: false,
        defaultValue: "detailed"),

    // With allowed values (dropdown in UIs)
    new("status", "Status Filter", "string", required: false,
        allowedValues: ["active", "inactive", "all"],
        defaultValue: "active"),

    // Date range
    new("fromDate", "Start Date", "datetime", required: true),
    new("toDate", "End Date", "datetime", required: true),

    // Numeric parameter
    new("minAmount", "Minimum Amount", "decimal", required: false,
        defaultValue: 0m,
        description: "Filter transactions above this amount")
];

Page Settings

Control page layout for renderers that support it:

csharp
public PageSettings PageSettings => new()
{
    Size = PageSize.A4,
    Orientation = PageOrientation.Landscape,
    MarginTop = 20,
    MarginBottom = 20,
    MarginLeft = 25,
    MarginRight = 25
};

Data Sources

Standard Data Source

For queries that return a complete dataset:

csharp
public class BillingDataSource : IReportDataSource<BillingData, BillingParameters>
{
    private readonly IBillingRepository _billing;

    public BillingDataSource(IBillingRepository billing) => _billing = billing;

    public async Task<BillingData> FetchDataAsync(
        BillingParameters parameters, CancellationToken ct)
    {
        var invoices = await _billing.GetByPeriodAsync(
            parameters.Year, parameters.Month, ct);

        return new BillingData
        {
            Year = parameters.Year,
            MonthName = new DateTime(parameters.Year, parameters.Month, 1).ToString("MMMM"),
            Invoices = invoices,
            TotalAmount = invoices.Sum(i => i.Total)
        };
    }

    // Optional: validate parameters before execution
    public async Task<Result> ValidateParametersAsync(
        BillingParameters parameters, CancellationToken ct)
    {
        if (parameters.Month < 1 || parameters.Month > 12)
            return Result.Failure("Month must be between 1 and 12");

        if (parameters.Year < 2020 || parameters.Year > DateTime.UtcNow.Year)
            return Result.Failure("Year is out of range");

        return Result.Success();
    }
}

Streaming Data Source

For large datasets that should be streamed (avoids loading everything into memory):

csharp
public class TransactionStreamSource
    : IStreamingReportDataSource<TransactionRecord, TransactionParams>
{
    private readonly ITransactionRepository _repo;

    public TransactionStreamSource(ITransactionRepository repo) => _repo = repo;

    public async IAsyncEnumerable<TransactionRecord> StreamDataAsync(
        TransactionParams parameters,
        [EnumeratorCancellation] CancellationToken ct)
    {
        await foreach (var tx in _repo.StreamByDateRangeAsync(
            parameters.FromDate, parameters.ToDate, ct))
        {
            yield return new TransactionRecord
            {
                Id = tx.Id,
                Date = tx.Date,
                Amount = tx.Amount,
                Description = tx.Description
            };
        }
    }

    public async Task<long> GetEstimatedCountAsync(
        TransactionParams parameters, CancellationToken ct)
    {
        return await _repo.CountByDateRangeAsync(
            parameters.FromDate, parameters.ToDate, ct);
    }
}

Multi-Format Templates

Templates can support multiple output formats. The renderer is selected at generation time:

csharp
public class ProductCatalogTemplate : IReportTemplate<CatalogData>
{
    public string TemplateId => "product-catalog";
    public string DisplayName => "Product Catalog";

    public IReadOnlyList<ReportFormat> SupportedFormats =>
        [ReportFormat.Pdf, ReportFormat.Excel, ReportFormat.Csv, ReportFormat.Html];

    // ...
}

Each format is rendered by its respective IReportRenderer:

FormatRendererPackageBest For
PdfQuestPDFGrydReports.Infrastructure.QuestPdfPrintable documents with layouts
ExcelClosedXMLGrydReports.Infrastructure.ClosedXmlSpreadsheets with formulas
CsvCsvHelperGrydReports.Infrastructure.CsvHelperData exports, ETL pipelines
HtmlBuilt-inGrydReports.CoreWeb previews, email embeds

Registration

Auto-Discovery

With AutoRegisterTemplates = true (default), templates are discovered automatically from assemblies:

csharp
opts.AutoRegisterTemplates = true;
opts.TemplateAssemblies.Add(typeof(MyTemplate).Assembly);

Manual Registration

csharp
builder.Services.AddSingleton<IReportTemplate<BillingData>, MonthlyBillingTemplate>();
builder.Services.AddScoped<IReportDataSource<BillingData, BillingParams>, BillingDataSource>();

Template Best Practices

DRY Templates

Extract common metadata logic into a base class:

csharp
public abstract class CompanyReportTemplate<TData> : IReportTemplate<TData>
{
    public abstract string TemplateId { get; }
    public abstract string DisplayName { get; }

    public ReportMetadata GetMetadata(TData data) => new()
    {
        Author = "Company Name",
        Keywords = "company, report",
        Title = GetTitle(data)
    };

    protected abstract string GetTitle(TData data);
}

Performance

For data sources that aggregate large datasets, prefer IStreamingReportDataSource to avoid out-of-memory issues. The streaming interface yields records one-by-one via IAsyncEnumerable.

Released under the MIT License.