Export Strategy Pattern in C#: Building a Scalable PDF Export Architecture with QuestPDF

From Ad-Hoc Export Logic to Maintainable Export Pipelines

Export functionality is one of the most underestimated architectural concerns in desktop applications.

Most systems begin with a simple requirement:

“We need a PDF export.”

The first implementation is usually straightforward. A developer generates a PDF directly from a button click, manually builds tables, fetches data inline, and writes the file to disk.

Initially, this works.

But over time, export requirements grow:

  • multiple export formats
  • optional columns
  • localization
  • branding
  • customer-specific layouts
  • background exports
  • preview support
  • email attachments
  • audit requirements

At that point, the export feature stops being “just a utility” and becomes a subsystem.

This article explores how Chrona approaches PDF export architecture using:

  • Strategy Pattern
  • dependency injection
  • QuestPDF
  • repository abstraction
  • layout orchestration
  • pre-render caching
  • UI-independent export pipelines

The goal is not only to generate PDFs, but to build an export architecture that remains maintainable as complexity grows.


Why Export Logic Often Becomes Fragile

Many desktop applications implement exports directly in the UI layer.

Typical examples:

private void ExportButton_Click(object sender, RoutedEventArgs e)
{
    var report = _repository.Load(...);

    var pdf = new PdfDocument();

    // render content

    pdf.Save("report.pdf");
}

This approach works for prototypes, but it creates long-term architectural problems.

The UI becomes responsible for:

  • data retrieval
  • formatting
  • layout decisions
  • export orchestration
  • filesystem handling
  • PDF library interaction

Over time, the export logic grows into a large procedural block that becomes difficult to extend safely.

Adding optional columns or supporting additional export formats usually leads to more branching logic and tighter coupling.

Chrona takes a different approach.

The export behavior is isolated behind a dedicated strategy implementation.


Strategy Pattern for Export Pipelines

Chrona models PDF export as its own export strategy.

The strategy encapsulates:

  • PDF-specific layout logic
  • rendering behavior
  • formatting rules
  • document orchestration

This keeps export concerns separate from:

  • view models
  • UI workflows
  • repository orchestration
  • application startup

The constructor already reflects this architecture clearly.

public PdfExportStrategy(
    IProjectRepository projectRepository,
    IBookingCodeRepository bookingCodeRepository,
    ISettingsService settingsService)
{
    _projectRepository = projectRepository;
    _bookingCodeRepository = bookingCodeRepository;
    _settingsService = settingsService;

    QuestPDF.Settings.License = LicenseType.Community;
}

Several important design decisions are visible here.

First, the strategy depends on interfaces instead of concrete implementations. This keeps the exporter testable and replaceable.

Second, data access happens through repositories rather than directly through UI state or database access.

Third, PDF-library-specific configuration remains localized inside the export strategy instead of leaking into application startup or view models.

That separation matters because export infrastructure evolves independently from presentation logic.


Why QuestPDF Fits This Architecture Well

One of the strongest aspects of Chrona’s export pipeline is the use of QuestPDF.

Traditional PDF generation libraries often rely heavily on coordinate-based rendering:

graphics.DrawText(..., x, y);
graphics.DrawRectangle(...);

This becomes difficult to maintain as layouts evolve.

QuestPDF uses a declarative layout approach instead.

The document structure reads like document intent.

Chrona creates documents like this:

var document = Document.Create(container =>
{
    container.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(2, Unit.Centimetre);
        page.DefaultTextStyle(x => x.FontSize(10));

        page.Header()
            .PaddingBottom(15);
    });
});

The footer follows the same pattern:

page.Footer()
    .AlignCenter()
    .Text(x =>
    {
        x.Span("Seite ").FontSize(8);
        x.CurrentPageNumber().FontSize(8);
        x.Span(" von ").FontSize(8);
        x.TotalPages().FontSize(8);
    });

This is significantly more maintainable than coordinate-driven rendering.

The layout is expressed structurally:

  • page
  • header
  • content
  • footer

Instead of manually calculating positions for every visual element.

That becomes especially valuable once reports become dynamic.


Comparing PDF Rendering Approaches

QuestPDF’s declarative model is fundamentally different from classic PDF-generation styles.

Coordinate-Based Rendering

Libraries such as older PDF toolkits often work like this:

graphics.DrawString(text, font, brush, x, y);

Advantages:

  • extremely precise control
  • useful for fixed forms
  • predictable rendering positions

Disadvantages:

  • hard to maintain
  • fragile during layout changes
  • difficult with dynamic content
  • manual pagination complexity

HTML-to-PDF Rendering

Another common approach is HTML rendering.

Advantages:

  • easy for web developers
  • CSS styling
  • reusable templates

Disadvantages:

  • inconsistent rendering engines
  • browser dependency
  • print-layout unpredictability
  • harder desktop integration

QuestPDF’s Declarative Layout

QuestPDF sits between both worlds.

Advantages:

  • layout-oriented instead of coordinate-oriented
  • strongly typed C#
  • deterministic rendering
  • composable structure
  • excellent for reports/tables/documents

Trade-offs:

  • learning curve
  • less raw pixel-level control
  • deeply nested layouts can become verbose

For business-report-style exports, Chrona’s architecture benefits strongly from this model.


Licensing Considerations

Why PDF Libraries Are an Architectural Decision

Once a desktop application becomes a real product, PDF generation is no longer just a technical concern. Licensing becomes part of the architecture decision.

That explicit configuration is important because QuestPDF uses a hybrid licensing model. According to the official licensing guide, the Community license is free for:

  • individuals
  • non-profits
  • open-source projects
  • organizations below $1M annual gross revenue

Larger organizations require a commercial license depending on the number of developers using QuestPDF. (QuestPDF)

One strong aspect of QuestPDF’s licensing approach is that functionality is not artificially restricted between tiers. The same rendering engine and features are available across Community, Professional, and Enterprise licensing. (QuestPDF)

Architecturally, this matters because it avoids “feature fragmentation” between environments. A team does not need separate implementations for development versus production capabilities.

QuestPDF also avoids runtime activation mechanisms:

  • no license keys
  • no online activation server
  • no runtime internet dependency

This is particularly valuable for desktop applications, CI/CD pipelines, offline deployments, and enterprise environments with restricted outbound connectivity. (QuestPDF)

Compared to other PDF approaches, the trade-offs become clearer.

Traditional commercial reporting suites such as Telerik or Syncfusion often provide large ecosystems and enterprise tooling, but introduce vendor lock-in and recurring licensing cost.

HTML-to-PDF engines such as IronPDF simplify HTML rendering workflows, but usually rely on embedded browser infrastructure and commercial licensing models. (IronPDF)

Older coordinate-based libraries such as PdfSharp or low-level PDF APIs provide permissive licensing and fine-grained control, but require significantly more manual layout logic.

QuestPDF occupies a middle position:

  • strongly typed C# layout
  • declarative document structure
  • modern API
  • deterministic rendering
  • no browser dependency
  • relatively frictionless deployment model

For Chrona’s reporting architecture, this aligns well with the Strategy-based export approach because the exporter owns rendering behavior while remaining isolated behind application abstractions.

One important nuance is historical licensing evolution. Earlier QuestPDF versions were distributed purely under MIT terms, while newer versions introduced the current hybrid licensing model. (GitHub)

That highlights an important engineering lesson:

external libraries are not only technical dependencies — they are long-term operational and legal dependencies.

For production systems, licensing should therefore be evaluated with the same seriousness as performance, maintainability, or API design.


Comparing Common PDF Licensing Models

MIT / Apache Style

Examples:

  • PdfSharpCore
  • some HTML generators

Advantages:

  • permissive
  • commercial-friendly
  • minimal restrictions

Disadvantages:

  • often fewer advanced layout features

LGPL / AGPL Models

Some PDF libraries use copyleft licenses.

Advantages:

  • powerful ecosystems

Disadvantages:

  • legal complexity
  • commercial redistribution concerns
  • possible source-disclosure implications

These licenses require careful legal review in enterprise products.


Commercial Licensing

Examples:

  • Syncfusion
  • Telerik Reporting
  • IronPDF

Advantages:

  • enterprise support
  • polished tooling
  • reporting ecosystems

Disadvantages:

  • licensing cost
  • vendor lock-in
  • runtime deployment considerations

QuestPDF’s Position

QuestPDF uses a community/commercial licensing model.

Architecturally, it offers several advantages:

  • modern API
  • clean layout abstraction
  • no HTML dependency
  • pure C#
  • excellent report composition

For Chrona’s export use case, the declarative API aligns extremely well with Strategy-based export architecture.


Dynamic Layouts Without Multiple Export Classes

One common export anti-pattern is creating separate exporters for every layout variation.

Examples:

  • ProjectPdfExportStrategy
  • ProjectWithoutStatusPdfExportStrategy
  • CompactProjectPdfExportStrategy

This quickly becomes unmaintainable.

Chrona instead keeps layout variability inside the rendering pipeline using settings-driven conditional structure.

if (_settingsService.ShowProjects)
{
    table.ColumnsDefinition(columns =>
    {
        columns.RelativeColumn(2);
        columns.RelativeColumn();
        columns.RelativeColumn();
        columns.RelativeColumn();
        columns.RelativeColumn(2);
        columns.RelativeColumn(2);
        columns.RelativeColumn(2);

        if (showReportStatus)
        {
            columns.RelativeColumn();
        }
    });
}

This is an important architectural detail.

The export strategy owns layout behavior.

The UI does not decide how many columns exist in the PDF.

The settings service provides configuration input, while the export strategy remains responsible for rendering decisions.

That separation keeps responsibilities clean.


Preparing Data Before Rendering

Another strong aspect of Chrona’s export architecture is explicit pre-render preparation.

Instead of repeatedly querying repositories during table rendering, the exporter prepares lookup caches first.

private async Task<Dictionary<int, Project>> LoadProjectCacheAsync(
    MonthlyReport report)
{
    var cache = new Dictionary<int, Project>();

    var projectIds = report.Entries
        .Where(e => e.ProjectId.HasValue)
        .Select(e => e.ProjectId!.Value)
        .Union(report.HoursByProject.Select(kvp => kvp.Key))
        .Distinct()
        .ToList();

    foreach (var projectId in projectIds)
    {
        var project = await _projectRepository
            .GetByIdAsync(projectId)
            .ConfigureAwait(false);

        if (project != null)
        {
            cache[projectId] = project;
        }
    }

    return cache;
}

This is architecturally important for several reasons.

First, rendering becomes predictable.

If rendering performs repository access inline during row creation, export runtime becomes harder to reason about.

Second, repeated lookups are avoided.

Without caching, large reports may repeatedly query the same project information during table rendering.

Third, rendering logic becomes cleaner.

The document pipeline can focus on layout instead of mixing rendering and retrieval concerns.

Chrona separates:

  • data preparation
  • rendering
  • layout orchestration

That separation improves maintainability significantly.


Export Output as Byte Arrays

Chrona generates PDFs into a memory stream:

using var stream = new MemoryStream();

document.GeneratePdf(stream);

return stream.ToArray();

This design keeps the export result flexible.

The resulting byte array can be:

  • saved to disk
  • attached to emails
  • returned from APIs
  • previewed
  • stored temporarily
  • uploaded elsewhere

This is an important architectural point.

The export strategy does not decide where the PDF goes.

It only produces the document.

That separation keeps the export pipeline reusable across different workflows.


When Byte Arrays Become a Limitation

Returning byte[] is pragmatic for normal report sizes.

But large exports eventually create memory pressure because the entire document must exist in memory simultaneously.

At larger scale, a streaming approach may be preferable:

Task GeneratePdfAsync(Stream output);

Streaming reduces peak memory usage and enables:

  • direct file streaming
  • HTTP response streaming
  • incremental generation

Chrona’s current approach is completely reasonable for desktop reporting scenarios, but this is an important scalability consideration for larger systems.


Why This Export Architecture Scales Well

Chrona’s export architecture succeeds because responsibilities are isolated correctly.

The UI coordinates workflows.

Repositories retrieve data.

Settings services expose configuration.

The export strategy owns rendering behavior.

QuestPDF owns PDF generation.

This prevents the export feature from collapsing into procedural UI logic.

The architecture also supports future evolution naturally.

Examples:

  • Excel export strategy
  • CSV export strategy
  • localization-aware exporters
  • tenant branding
  • audit-aware export metadata
  • background export jobs
  • cloud upload integration

None of these require rewriting the core application structure.

That is the real value of Strategy Pattern in export systems.


Testing Implications

This architecture is also highly testable.

The export strategy can be tested independently from WPF.

Examples:

  • verify optional columns
  • verify footer rendering
  • verify settings-driven layouts
  • verify project lookup behavior
  • verify empty-report handling

Because dependencies are injected via interfaces, repositories and settings can be mocked cleanly.

This is dramatically better than testing export logic hidden inside UI event handlers.


Trade-Offs and Honest Caveats

A serious architecture discussion must acknowledge trade-offs.

QuestPDF introduces a learning curve. Developers unfamiliar with declarative layout composition may initially find nested containers verbose.

Dependency injection adds setup overhead compared to direct instantiation.

Strategy-based exports require architectural discipline. If developers bypass the strategy layer and place rendering logic directly into view models again, the design degrades quickly.

Large PDF rendering pipelines may also require performance tuning for:

  • huge tables
  • image-heavy reports
  • repeated rendering operations

Finally, licensing must always be reviewed carefully before shipping commercial desktop software.

The important point is not that this architecture is “perfect.”

The important point is that it scales far better than ad-hoc export logic once the application becomes a real product.


Final Thoughts

Export systems are rarely “just utility code.”

Once reports become business-critical artifacts, export pipelines become part of the product architecture itself.

Chrona’s PDF export implementation demonstrates several strong architectural decisions:

  • export behavior isolated behind strategies
  • dependency injection via interfaces
  • declarative document composition
  • centralized rendering responsibility
  • settings-driven layout variability
  • pre-render caching
  • reusable binary output

The result is an export pipeline that remains maintainable even as requirements evolve.

That is the real architectural lesson:

good export systems are not built around PDFs.

They are built around separation of concerns, predictable rendering boundaries, and controlled variability.

Von admin