{"id":207,"date":"2026-05-09T09:10:40","date_gmt":"2026-05-09T09:10:40","guid":{"rendered":"https:\/\/www.fabricioruch.ch\/?p=207"},"modified":"2026-05-09T10:09:49","modified_gmt":"2026-05-09T10:09:49","slug":"export-strategy-pattern-in-c-building-a-scalable-pdf-export-architecture-with-questpdf","status":"publish","type":"post","link":"https:\/\/www.fabricioruch.ch\/?p=207","title":{"rendered":"Export Strategy Pattern in C#: Building a Scalable PDF Export Architecture with QuestPDF"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">From Ad-Hoc Export Logic to Maintainable Export Pipelines<\/h2>\n\n\n\n<p>Export functionality is one of the most underestimated architectural concerns in desktop applications.<\/p>\n\n\n\n<p>Most systems begin with a simple requirement:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u201cWe need a PDF export.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Initially, this works.<\/p>\n\n\n\n<p>But over time, export requirements grow:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>multiple export formats<\/li>\n\n\n\n<li>optional columns<\/li>\n\n\n\n<li>localization<\/li>\n\n\n\n<li>branding<\/li>\n\n\n\n<li>customer-specific layouts<\/li>\n\n\n\n<li>background exports<\/li>\n\n\n\n<li>preview support<\/li>\n\n\n\n<li>email attachments<\/li>\n\n\n\n<li>audit requirements<\/li>\n<\/ul>\n\n\n\n<p>At that point, the export feature stops being \u201cjust a utility\u201d and becomes a subsystem.<\/p>\n\n\n\n<p>This article explores how Chrona approaches PDF export architecture using:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Strategy Pattern<\/li>\n\n\n\n<li>dependency injection<\/li>\n\n\n\n<li>QuestPDF<\/li>\n\n\n\n<li>repository abstraction<\/li>\n\n\n\n<li>layout orchestration<\/li>\n\n\n\n<li>pre-render caching<\/li>\n\n\n\n<li>UI-independent export pipelines<\/li>\n<\/ul>\n\n\n\n<p>The goal is not only to generate PDFs, but to build an export architecture that remains maintainable as complexity grows.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Why Export Logic Often Becomes Fragile<\/h1>\n\n\n\n<p>Many desktop applications implement exports directly in the UI layer.<\/p>\n\n\n\n<p>Typical examples:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private void ExportButton_Click(object sender, RoutedEventArgs e)\n{\n    var report = _repository.Load(...);\n\n    var pdf = new PdfDocument();\n\n    \/\/ render content\n\n    pdf.Save(\"report.pdf\");\n}\n<\/code><\/pre>\n\n\n\n<p>This approach works for prototypes, but it creates long-term architectural problems.<\/p>\n\n\n\n<p>The UI becomes responsible for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>data retrieval<\/li>\n\n\n\n<li>formatting<\/li>\n\n\n\n<li>layout decisions<\/li>\n\n\n\n<li>export orchestration<\/li>\n\n\n\n<li>filesystem handling<\/li>\n\n\n\n<li>PDF library interaction<\/li>\n<\/ul>\n\n\n\n<p>Over time, the export logic grows into a large procedural block that becomes difficult to extend safely.<\/p>\n\n\n\n<p>Adding optional columns or supporting additional export formats usually leads to more branching logic and tighter coupling.<\/p>\n\n\n\n<p>Chrona takes a different approach.<\/p>\n\n\n\n<p>The export behavior is isolated behind a dedicated strategy implementation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Strategy Pattern for Export Pipelines<\/h1>\n\n\n\n<p>Chrona models PDF export as its own export strategy.<\/p>\n\n\n\n<p>The strategy encapsulates:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PDF-specific layout logic<\/li>\n\n\n\n<li>rendering behavior<\/li>\n\n\n\n<li>formatting rules<\/li>\n\n\n\n<li>document orchestration<\/li>\n<\/ul>\n\n\n\n<p>This keeps export concerns separate from:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>view models<\/li>\n\n\n\n<li>UI workflows<\/li>\n\n\n\n<li>repository orchestration<\/li>\n\n\n\n<li>application startup<\/li>\n<\/ul>\n\n\n\n<p>The constructor already reflects this architecture clearly.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public PdfExportStrategy(\n    IProjectRepository projectRepository,\n    IBookingCodeRepository bookingCodeRepository,\n    ISettingsService settingsService)\n{\n    _projectRepository = projectRepository;\n    _bookingCodeRepository = bookingCodeRepository;\n    _settingsService = settingsService;\n\n    QuestPDF.Settings.License = LicenseType.Community;\n}\n<\/code><\/pre>\n\n\n\n<p>Several important design decisions are visible here.<\/p>\n\n\n\n<p>First, the strategy depends on interfaces instead of concrete implementations. This keeps the exporter testable and replaceable.<\/p>\n\n\n\n<p>Second, data access happens through repositories rather than directly through UI state or database access.<\/p>\n\n\n\n<p>Third, PDF-library-specific configuration remains localized inside the export strategy instead of leaking into application startup or view models.<\/p>\n\n\n\n<p>That separation matters because export infrastructure evolves independently from presentation logic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Why QuestPDF Fits This Architecture Well<\/h1>\n\n\n\n<p>One of the strongest aspects of Chrona\u2019s export pipeline is the use of QuestPDF.<\/p>\n\n\n\n<p>Traditional PDF generation libraries often rely heavily on coordinate-based rendering:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>graphics.DrawText(..., x, y);\ngraphics.DrawRectangle(...);\n<\/code><\/pre>\n\n\n\n<p>This becomes difficult to maintain as layouts evolve.<\/p>\n\n\n\n<p>QuestPDF uses a declarative layout approach instead.<\/p>\n\n\n\n<p>The document structure reads like document intent.<\/p>\n\n\n\n<p>Chrona creates documents like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var document = Document.Create(container =&gt;\n{\n    container.Page(page =&gt;\n    {\n        page.Size(PageSizes.A4);\n        page.Margin(2, Unit.Centimetre);\n        page.DefaultTextStyle(x =&gt; x.FontSize(10));\n\n        page.Header()\n            .PaddingBottom(15);\n    });\n});\n<\/code><\/pre>\n\n\n\n<p>The footer follows the same pattern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>page.Footer()\n    .AlignCenter()\n    .Text(x =&gt;\n    {\n        x.Span(\"Seite \").FontSize(8);\n        x.CurrentPageNumber().FontSize(8);\n        x.Span(\" von \").FontSize(8);\n        x.TotalPages().FontSize(8);\n    });\n<\/code><\/pre>\n\n\n\n<p>This is significantly more maintainable than coordinate-driven rendering.<\/p>\n\n\n\n<p>The layout is expressed structurally:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>page<\/li>\n\n\n\n<li>header<\/li>\n\n\n\n<li>content<\/li>\n\n\n\n<li>footer<\/li>\n<\/ul>\n\n\n\n<p>Instead of manually calculating positions for every visual element.<\/p>\n\n\n\n<p>That becomes especially valuable once reports become dynamic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Comparing PDF Rendering Approaches<\/h1>\n\n\n\n<p>QuestPDF\u2019s declarative model is fundamentally different from classic PDF-generation styles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Coordinate-Based Rendering<\/h2>\n\n\n\n<p>Libraries such as older PDF toolkits often work like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>graphics.DrawString(text, font, brush, x, y);\n<\/code><\/pre>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>extremely precise control<\/li>\n\n\n\n<li>useful for fixed forms<\/li>\n\n\n\n<li>predictable rendering positions<\/li>\n<\/ul>\n\n\n\n<p>Disadvantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>hard to maintain<\/li>\n\n\n\n<li>fragile during layout changes<\/li>\n\n\n\n<li>difficult with dynamic content<\/li>\n\n\n\n<li>manual pagination complexity<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">HTML-to-PDF Rendering<\/h2>\n\n\n\n<p>Another common approach is HTML rendering.<\/p>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>easy for web developers<\/li>\n\n\n\n<li>CSS styling<\/li>\n\n\n\n<li>reusable templates<\/li>\n<\/ul>\n\n\n\n<p>Disadvantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>inconsistent rendering engines<\/li>\n\n\n\n<li>browser dependency<\/li>\n\n\n\n<li>print-layout unpredictability<\/li>\n\n\n\n<li>harder desktop integration<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">QuestPDF\u2019s Declarative Layout<\/h2>\n\n\n\n<p>QuestPDF sits between both worlds.<\/p>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>layout-oriented instead of coordinate-oriented<\/li>\n\n\n\n<li>strongly typed C#<\/li>\n\n\n\n<li>deterministic rendering<\/li>\n\n\n\n<li>composable structure<\/li>\n\n\n\n<li>excellent for reports\/tables\/documents<\/li>\n<\/ul>\n\n\n\n<p>Trade-offs:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>learning curve<\/li>\n\n\n\n<li>less raw pixel-level control<\/li>\n\n\n\n<li>deeply nested layouts can become verbose<\/li>\n<\/ul>\n\n\n\n<p>For business-report-style exports, Chrona\u2019s architecture benefits strongly from this model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Licensing Considerations<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Why PDF Libraries Are an Architectural Decision<\/h2>\n\n\n\n<p>Once a desktop application becomes a real product, PDF generation is no longer just a technical concern. Licensing becomes part of the architecture decision.<\/p>\n\n\n\n<p>That explicit configuration is important because QuestPDF uses a hybrid licensing model. According to the official licensing guide, the Community license is free for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>individuals<\/li>\n\n\n\n<li>non-profits<\/li>\n\n\n\n<li>open-source projects<\/li>\n\n\n\n<li>organizations below $1M annual gross revenue<\/li>\n<\/ul>\n\n\n\n<p>Larger organizations require a commercial license depending on the number of developers using QuestPDF. (<a href=\"https:\/\/www.questpdf.com\/license\/?utm_source=chatgpt.com\">QuestPDF<\/a>)<\/p>\n\n\n\n<p>One strong aspect of QuestPDF\u2019s 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. (<a href=\"https:\/\/www.questpdf.com\/license\/guide.html?utm_source=chatgpt.com\">QuestPDF<\/a>)<\/p>\n\n\n\n<p>Architecturally, this matters because it avoids \u201cfeature fragmentation\u201d between environments. A team does not need separate implementations for development versus production capabilities.<\/p>\n\n\n\n<p>QuestPDF also avoids runtime activation mechanisms:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>no license keys<\/li>\n\n\n\n<li>no online activation server<\/li>\n\n\n\n<li>no runtime internet dependency<\/li>\n<\/ul>\n\n\n\n<p>This is particularly valuable for desktop applications, CI\/CD pipelines, offline deployments, and enterprise environments with restricted outbound connectivity. (<a href=\"https:\/\/www.questpdf.com\/license\/?utm_source=chatgpt.com\">QuestPDF<\/a>)<\/p>\n\n\n\n<p>Compared to other PDF approaches, the trade-offs become clearer.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>HTML-to-PDF engines such as IronPDF simplify HTML rendering workflows, but usually rely on embedded browser infrastructure and commercial licensing models. (<a href=\"https:\/\/ironpdf.com\/blog\/migration-guides\/migrate-from-questpdf-to-ironpdf\/?utm_source=chatgpt.com\">IronPDF<\/a>)<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>QuestPDF occupies a middle position:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>strongly typed C# layout<\/li>\n\n\n\n<li>declarative document structure<\/li>\n\n\n\n<li>modern API<\/li>\n\n\n\n<li>deterministic rendering<\/li>\n\n\n\n<li>no browser dependency<\/li>\n\n\n\n<li>relatively frictionless deployment model<\/li>\n<\/ul>\n\n\n\n<p>For Chrona\u2019s reporting architecture, this aligns well with the Strategy-based export approach because the exporter owns rendering behavior while remaining isolated behind application abstractions.<\/p>\n\n\n\n<p>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. (<a href=\"https:\/\/github.com\/QuestPDF\/QuestPDF\/discussions\/491?utm_source=chatgpt.com\">GitHub<\/a>)<\/p>\n\n\n\n<p>That highlights an important engineering lesson:<\/p>\n\n\n\n<p>external libraries are not only technical dependencies \u2014 they are long-term operational and legal dependencies.<\/p>\n\n\n\n<p>For production systems, licensing should therefore be evaluated with the same seriousness as performance, maintainability, or API design.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Comparing Common PDF Licensing Models<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">MIT \/ Apache Style<\/h2>\n\n\n\n<p>Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PdfSharpCore<\/li>\n\n\n\n<li>some HTML generators<\/li>\n<\/ul>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>permissive<\/li>\n\n\n\n<li>commercial-friendly<\/li>\n\n\n\n<li>minimal restrictions<\/li>\n<\/ul>\n\n\n\n<p>Disadvantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>often fewer advanced layout features<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">LGPL \/ AGPL Models<\/h2>\n\n\n\n<p>Some PDF libraries use copyleft licenses.<\/p>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>powerful ecosystems<\/li>\n<\/ul>\n\n\n\n<p>Disadvantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>legal complexity<\/li>\n\n\n\n<li>commercial redistribution concerns<\/li>\n\n\n\n<li>possible source-disclosure implications<\/li>\n<\/ul>\n\n\n\n<p>These licenses require careful legal review in enterprise products.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Commercial Licensing<\/h2>\n\n\n\n<p>Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Syncfusion<\/li>\n\n\n\n<li>Telerik Reporting<\/li>\n\n\n\n<li>IronPDF<\/li>\n<\/ul>\n\n\n\n<p>Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>enterprise support<\/li>\n\n\n\n<li>polished tooling<\/li>\n\n\n\n<li>reporting ecosystems<\/li>\n<\/ul>\n\n\n\n<p>Disadvantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>licensing cost<\/li>\n\n\n\n<li>vendor lock-in<\/li>\n\n\n\n<li>runtime deployment considerations<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">QuestPDF\u2019s Position<\/h2>\n\n\n\n<p>QuestPDF uses a community\/commercial licensing model.<\/p>\n\n\n\n<p>Architecturally, it offers several advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>modern API<\/li>\n\n\n\n<li>clean layout abstraction<\/li>\n\n\n\n<li>no HTML dependency<\/li>\n\n\n\n<li>pure C#<\/li>\n\n\n\n<li>excellent report composition<\/li>\n<\/ul>\n\n\n\n<p>For Chrona\u2019s export use case, the declarative API aligns extremely well with Strategy-based export architecture.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Dynamic Layouts Without Multiple Export Classes<\/h1>\n\n\n\n<p>One common export anti-pattern is creating separate exporters for every layout variation.<\/p>\n\n\n\n<p>Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>ProjectPdfExportStrategy<\/code><\/li>\n\n\n\n<li><code>ProjectWithoutStatusPdfExportStrategy<\/code><\/li>\n\n\n\n<li><code>CompactProjectPdfExportStrategy<\/code><\/li>\n<\/ul>\n\n\n\n<p>This quickly becomes unmaintainable.<\/p>\n\n\n\n<p>Chrona instead keeps layout variability inside the rendering pipeline using settings-driven conditional structure.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (_settingsService.ShowProjects)\n{\n    table.ColumnsDefinition(columns =&gt;\n    {\n        columns.RelativeColumn(2);\n        columns.RelativeColumn();\n        columns.RelativeColumn();\n        columns.RelativeColumn();\n        columns.RelativeColumn(2);\n        columns.RelativeColumn(2);\n        columns.RelativeColumn(2);\n\n        if (showReportStatus)\n        {\n            columns.RelativeColumn();\n        }\n    });\n}\n<\/code><\/pre>\n\n\n\n<p>This is an important architectural detail.<\/p>\n\n\n\n<p>The export strategy owns layout behavior.<\/p>\n\n\n\n<p>The UI does not decide how many columns exist in the PDF.<\/p>\n\n\n\n<p>The settings service provides configuration input, while the export strategy remains responsible for rendering decisions.<\/p>\n\n\n\n<p>That separation keeps responsibilities clean.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Preparing Data Before Rendering<\/h1>\n\n\n\n<p>Another strong aspect of Chrona\u2019s export architecture is explicit pre-render preparation.<\/p>\n\n\n\n<p>Instead of repeatedly querying repositories during table rendering, the exporter prepares lookup caches first.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private async Task&lt;Dictionary&lt;int, Project&gt;&gt; LoadProjectCacheAsync(\n    MonthlyReport report)\n{\n    var cache = new Dictionary&lt;int, Project&gt;();\n\n    var projectIds = report.Entries\n        .Where(e =&gt; e.ProjectId.HasValue)\n        .Select(e =&gt; e.ProjectId!.Value)\n        .Union(report.HoursByProject.Select(kvp =&gt; kvp.Key))\n        .Distinct()\n        .ToList();\n\n    foreach (var projectId in projectIds)\n    {\n        var project = await _projectRepository\n            .GetByIdAsync(projectId)\n            .ConfigureAwait(false);\n\n        if (project != null)\n        {\n            cache&#91;projectId] = project;\n        }\n    }\n\n    return cache;\n}\n<\/code><\/pre>\n\n\n\n<p>This is architecturally important for several reasons.<\/p>\n\n\n\n<p>First, rendering becomes predictable.<\/p>\n\n\n\n<p>If rendering performs repository access inline during row creation, export runtime becomes harder to reason about.<\/p>\n\n\n\n<p>Second, repeated lookups are avoided.<\/p>\n\n\n\n<p>Without caching, large reports may repeatedly query the same project information during table rendering.<\/p>\n\n\n\n<p>Third, rendering logic becomes cleaner.<\/p>\n\n\n\n<p>The document pipeline can focus on layout instead of mixing rendering and retrieval concerns.<\/p>\n\n\n\n<p>Chrona separates:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>data preparation<\/li>\n\n\n\n<li>rendering<\/li>\n\n\n\n<li>layout orchestration<\/li>\n<\/ul>\n\n\n\n<p>That separation improves maintainability significantly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Export Output as Byte Arrays<\/h1>\n\n\n\n<p>Chrona generates PDFs into a memory stream:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using var stream = new MemoryStream();\n\ndocument.GeneratePdf(stream);\n\nreturn stream.ToArray();\n<\/code><\/pre>\n\n\n\n<p>This design keeps the export result flexible.<\/p>\n\n\n\n<p>The resulting byte array can be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>saved to disk<\/li>\n\n\n\n<li>attached to emails<\/li>\n\n\n\n<li>returned from APIs<\/li>\n\n\n\n<li>previewed<\/li>\n\n\n\n<li>stored temporarily<\/li>\n\n\n\n<li>uploaded elsewhere<\/li>\n<\/ul>\n\n\n\n<p>This is an important architectural point.<\/p>\n\n\n\n<p>The export strategy does not decide where the PDF goes.<\/p>\n\n\n\n<p>It only produces the document.<\/p>\n\n\n\n<p>That separation keeps the export pipeline reusable across different workflows.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">When Byte Arrays Become a Limitation<\/h1>\n\n\n\n<p>Returning <code>byte[]<\/code> is pragmatic for normal report sizes.<\/p>\n\n\n\n<p>But large exports eventually create memory pressure because the entire document must exist in memory simultaneously.<\/p>\n\n\n\n<p>At larger scale, a streaming approach may be preferable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Task GeneratePdfAsync(Stream output);\n<\/code><\/pre>\n\n\n\n<p>Streaming reduces peak memory usage and enables:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>direct file streaming<\/li>\n\n\n\n<li>HTTP response streaming<\/li>\n\n\n\n<li>incremental generation<\/li>\n<\/ul>\n\n\n\n<p>Chrona\u2019s current approach is completely reasonable for desktop reporting scenarios, but this is an important scalability consideration for larger systems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Why This Export Architecture Scales Well<\/h1>\n\n\n\n<p>Chrona\u2019s export architecture succeeds because responsibilities are isolated correctly.<\/p>\n\n\n\n<p>The UI coordinates workflows.<\/p>\n\n\n\n<p>Repositories retrieve data.<\/p>\n\n\n\n<p>Settings services expose configuration.<\/p>\n\n\n\n<p>The export strategy owns rendering behavior.<\/p>\n\n\n\n<p>QuestPDF owns PDF generation.<\/p>\n\n\n\n<p>This prevents the export feature from collapsing into procedural UI logic.<\/p>\n\n\n\n<p>The architecture also supports future evolution naturally.<\/p>\n\n\n\n<p>Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Excel export strategy<\/li>\n\n\n\n<li>CSV export strategy<\/li>\n\n\n\n<li>localization-aware exporters<\/li>\n\n\n\n<li>tenant branding<\/li>\n\n\n\n<li>audit-aware export metadata<\/li>\n\n\n\n<li>background export jobs<\/li>\n\n\n\n<li>cloud upload integration<\/li>\n<\/ul>\n\n\n\n<p>None of these require rewriting the core application structure.<\/p>\n\n\n\n<p>That is the real value of Strategy Pattern in export systems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Testing Implications<\/h1>\n\n\n\n<p>This architecture is also highly testable.<\/p>\n\n\n\n<p>The export strategy can be tested independently from WPF.<\/p>\n\n\n\n<p>Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>verify optional columns<\/li>\n\n\n\n<li>verify footer rendering<\/li>\n\n\n\n<li>verify settings-driven layouts<\/li>\n\n\n\n<li>verify project lookup behavior<\/li>\n\n\n\n<li>verify empty-report handling<\/li>\n<\/ul>\n\n\n\n<p>Because dependencies are injected via interfaces, repositories and settings can be mocked cleanly.<\/p>\n\n\n\n<p>This is dramatically better than testing export logic hidden inside UI event handlers.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Trade-Offs and Honest Caveats<\/h1>\n\n\n\n<p>A serious architecture discussion must acknowledge trade-offs.<\/p>\n\n\n\n<p>QuestPDF introduces a learning curve. Developers unfamiliar with declarative layout composition may initially find nested containers verbose.<\/p>\n\n\n\n<p>Dependency injection adds setup overhead compared to direct instantiation.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Large PDF rendering pipelines may also require performance tuning for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>huge tables<\/li>\n\n\n\n<li>image-heavy reports<\/li>\n\n\n\n<li>repeated rendering operations<\/li>\n<\/ul>\n\n\n\n<p>Finally, licensing must always be reviewed carefully before shipping commercial desktop software.<\/p>\n\n\n\n<p>The important point is not that this architecture is \u201cperfect.\u201d<\/p>\n\n\n\n<p>The important point is that it scales far better than ad-hoc export logic once the application becomes a real product.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Final Thoughts<\/h1>\n\n\n\n<p>Export systems are rarely \u201cjust utility code.\u201d<\/p>\n\n\n\n<p>Once reports become business-critical artifacts, export pipelines become part of the product architecture itself.<\/p>\n\n\n\n<p>Chrona\u2019s PDF export implementation demonstrates several strong architectural decisions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>export behavior isolated behind strategies<\/li>\n\n\n\n<li>dependency injection via interfaces<\/li>\n\n\n\n<li>declarative document composition<\/li>\n\n\n\n<li>centralized rendering responsibility<\/li>\n\n\n\n<li>settings-driven layout variability<\/li>\n\n\n\n<li>pre-render caching<\/li>\n\n\n\n<li>reusable binary output<\/li>\n<\/ul>\n\n\n\n<p>The result is an export pipeline that remains maintainable even as requirements evolve.<\/p>\n\n\n\n<p>That is the real architectural lesson:<\/p>\n\n\n\n<p>good export systems are not built around PDFs.<\/p>\n\n\n\n<p>They are built around separation of concerns, predictable rendering boundaries, and controlled variability.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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: \u201cWe need a PDF export.\u201d The first implementation is usually straightforward. A developer generates a PDF directly from a button click, manually builds tables, fetches data inline, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[],"class_list":["post-207","post","type-post","status-publish","format-standard","hentry","category-csharplibs"],"_links":{"self":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=207"}],"version-history":[{"count":1,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/207\/revisions"}],"predecessor-version":[{"id":208,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=\/wp\/v2\/posts\/207\/revisions\/208"}],"wp:attachment":[{"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fabricioruch.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}