Skip to content

Document Generation

Granit.DocumentGeneration extends the Templating pipeline with binary document output: HTML→PDF via PuppeteerSharp headless Chromium, Excel via ClosedXML, and PDF/A-3b conversion for long-term archival and electronic invoicing (Factur-X / ZUGFeRD EN 16931).

  • DirectoryGranit.DocumentGeneration/ IDocumentGenerator facade, 2-stage pipeline (text render + binary conversion)
    • Granit.DocumentGeneration.Pdf PuppeteerSharp headless Chromium, PdfRenderOptions, PDF/A-3b
    • Granit.DocumentGeneration.Excel ClosedXML engine, Base64-encoded XLSX templates
PackageRoleDepends on
Granit.DocumentGenerationIDocumentGenerator facade, IDocumentRenderer abstractionGranit.Templating
Granit.DocumentGeneration.PdfPuppeteerSharp PDF renderer, IPdfAConverterGranit.DocumentGeneration
Granit.DocumentGeneration.ExcelClosedXML ITemplateEngine (direct binary output)Granit.Templating
[DependsOn(
typeof(GranitDocumentGenerationPdfModule),
typeof(GranitDocumentGenerationExcelModule),
typeof(GranitTemplatingScribanModule))]
public class AppModule : GranitModule { }

IDocumentGenerator extends the text pipeline with a binary conversion step:

flowchart LR
    subgraph "Text Pipeline"
        DATA[TData] --> ENRICH[Enrichers]
        ENRICH --> RESOLVE[Resolver Chain]
        RESOLVE --> ENGINE[ITemplateEngine]
    end
    ENGINE -->|TextRenderedContent| RENDERER["IDocumentRenderer<br/>(PuppeteerSharp)"]
    ENGINE -->|BinaryRenderedContent| RESULT
    RENDERER --> RESULT[DocumentResult]

Two paths through the pipeline:

PathEngine outputNext stepExample
HTML-basedTextRenderedContentIDocumentRenderer converts HTML to binaryInvoice PDF (Scriban HTML → Chromium PDF)
Native binaryBinaryRenderedContentDirect output, no renderer neededExcel report (ClosedXML XLSX)
public sealed class InvoiceTemplateType
: DocumentTemplateType<InvoiceDocumentData>
{
public override string Name => "Billing.Invoice";
public override DocumentFormat DefaultFormat => DocumentFormat.Pdf;
}
public sealed record InvoiceDocumentData(
string InvoiceNumber,
DateTimeOffset InvoiceDate,
string CustomerName,
string CustomerAddress,
IReadOnlyList<InvoiceLineItem> Lines,
decimal TotalExclVat,
decimal VatAmount,
decimal TotalInclVat,
string PaymentUrl,
string? PaymentQrCodeSvg = null);
public sealed class InvoiceService(IDocumentGenerator generator)
{
public async Task<DocumentResult> GenerateInvoicePdfAsync(
InvoiceDocumentData data,
CancellationToken cancellationToken)
{
DocumentResult result = await generator
.GenerateAsync(new InvoiceTemplateType(), data, cancellationToken: cancellationToken)
.ConfigureAwait(false);
// result.Content contains PDF bytes
// result.Format == DocumentFormat.Pdf
return result;
}
}

Excel templates use Base64-encoded XLSX workbooks stored as template content. Placeholders ({{model.property}}) in string cells are replaced with data values. Nested objects use dot notation ({{model.address.city}}), arrays use bracket notation ({{model.lines[0].amount}}).

public sealed class MonthlyReportTemplateType
: DocumentTemplateType<MonthlyReportData>
{
public override string Name => "Reporting.MonthlyReport";
public override DocumentFormat DefaultFormat => DocumentFormat.Excel;
}

The ClosedXmlTemplateEngine returns BinaryRenderedContent directly — no IDocumentRenderer is needed for Excel output.

For long-term archival and electronic invoicing (Factur-X / ZUGFeRD EN 16931), use IPdfAConverter to convert standard PDFs to PDF/A-3b:

public async Task<DocumentResult> GenerateArchivalInvoiceAsync(
InvoiceDocumentData data,
IPdfAConverter pdfAConverter,
IDocumentGenerator generator,
CancellationToken cancellationToken)
{
DocumentResult pdf = await generator
.GenerateAsync(new InvoiceTemplateType(), data, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return await pdfAConverter
.ConvertToPdfAAsync(pdf, new PdfAConversionOptions(), cancellationToken)
.ConfigureAwait(false);
}

Bound from configuration section DocumentGeneration:Pdf:

{
"DocumentGeneration": {
"Pdf": {
"PaperFormat": "A4",
"Landscape": false,
"MarginTop": "10mm",
"MarginBottom": "10mm",
"MarginLeft": "10mm",
"MarginRight": "10mm",
"HeaderTemplate": null,
"FooterTemplate": "<div style='font-size:9px;text-align:center;width:100%'><span class='pageNumber'></span>/<span class='totalPages'></span></div>",
"PrintBackground": true,
"ChromiumExecutablePath": null,
"MaxConcurrentPages": 4
}
}
}
PropertyDefaultDescription
PaperFormat"A4"Paper size ("A4", "A5", "Letter")
LandscapefalseLandscape orientation
MarginTop / Bottom / Left / Right"10mm"Margins in CSS units
HeaderTemplatenullHTML header (supports pageNumber, totalPages classes)
FooterTemplatenullHTML footer (same classes as header)
PrintBackgroundtruePrint background graphics
ChromiumExecutablePathnullCustom Chromium path (auto-download if null)
MaxConcurrentPages4Max parallel Chromium tabs (1—32)
CategoryKey typesPackage
ModuleGranitDocumentGenerationModule, GranitDocumentGenerationPdfModule, GranitDocumentGenerationExcelModule
KeysDocumentTemplateType<TData>, DocumentFormatGranit.DocumentGeneration
GeneratorIDocumentGenerator, IDocumentRenderer, DocumentResultGranit.DocumentGeneration
PDFPdfRenderOptions, IPdfAConverter, PdfAConversionOptionsGranit.DocumentGeneration.Pdf
ExtensionsAddGranitDocumentGeneration(), AddGranitDocumentGenerationPdf(), AddGranitDocumentGenerationExcel()