Advanced Patterns
Master complex data binding scenarios, dynamic layouts, and reusable template patterns.
Learning Objectives
By the end of this article, you’ll be able to:
- Build dynamic multi-section documents
- Create reusable template components
- Handle complex nested data structures
- Implement conditional layouts
- Use data-driven page breaks
- Build master-detail reports
- Create grouped and aggregated displays
Pattern 1: Dynamic Multi-Section Documents
Create documents with variable numbers of sections based on data.
C# Code:
doc.Params["model"] = new
{
documentTitle = "Comprehensive Report",
sections = new[]
{
new
{
sectionType = "summary",
title = "Executive Summary",
content = "Key findings and recommendations...",
showChart = false
},
new
{
sectionType = "detailed",
title = "Detailed Analysis",
content = "In-depth analysis of the data...",
showChart = true,
chartData = new[] { 10, 20, 30, 40 }
},
new
{
sectionType = "table",
title = "Financial Data",
content = "",
showChart = false,
tableData = new[]
{
new { category = "Revenue", amount = 1000000 },
new { category = "Expenses", amount = 750000 }
}
}
}
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>{{model.documentTitle}}</title>
<style>
body {
font-family: Helvetica, sans-serif;
margin: 40pt;
}
.section {
page-break-before: always;
margin-bottom: 30pt;
}
.section:first-child {
page-break-before: auto;
}
h2 {
color: #2563eb;
border-bottom: 2pt solid #2563eb;
padding-bottom: 10pt;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #2563eb;
color: white;
padding: 10pt;
}
td {
padding: 8pt;
border-bottom: 1pt solid #e5e7eb;
}
</style>
</head>
<body>
<h1>{{model.documentTitle}}</h1>
{{#each model.sections}}
<div class="section">
<h2>{{this.title}}</h2>
{{#if this.sectionType == 'summary'}}
<div class="summary">
<p>{{this.content}}</p>
</div>
{{else if this.sectionType == 'detailed'}}
<div class="detailed">
<p>{{this.content}}</p>
{{#if this.showChart}}
<div class="chart-placeholder">
[Chart would be rendered here]
</div>
{{/if}}
</div>
{{else if this.sectionType == 'table'}}
<table>
<thead>
<tr>
<th>Category</th>
<th style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>
{{#each this.tableData}}
<tr>
<td>{{this.category}}</td>
<td style="text-align: right;">{{format(this.amount, 'C0')}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</div>
{{/each}}
</body>
</html>
Pattern 2: Master-Detail Reports
Display hierarchical data with master records and their associated details.
C# Code:
doc.Params["model"] = new
{
reportTitle = "Customer Orders Report",
reportDate = DateTime.Now,
customers = new[]
{
new
{
customerId = "CUST-001",
customerName = "Acme Corporation",
email = "contact@acme.com",
phone = "555-1234",
totalOrders = 3,
totalSpent = 15000.00,
orders = new[]
{
new
{
orderNumber = "ORD-001",
orderDate = new DateTime(2025, 1, 10),
status = "Delivered",
items = new[]
{
new { product = "Widget A", quantity = 10, price = 100.00, total = 1000.00 },
new { product = "Widget B", quantity = 5, price = 200.00, total = 1000.00 }
},
orderTotal = 2000.00
},
new
{
orderNumber = "ORD-002",
orderDate = new DateTime(2025, 1, 15),
status = "Processing",
items = new[]
{
new { product = "Widget C", quantity = 20, price = 150.00, total = 3000.00 }
},
orderTotal = 3000.00
}
}
},
new
{
customerId = "CUST-002",
customerName = "Global Industries",
email = "orders@global.com",
phone = "555-5678",
totalOrders = 1,
totalSpent = 5000.00,
orders = new[]
{
new
{
orderNumber = "ORD-003",
orderDate = new DateTime(2025, 1, 12),
status = "Shipped",
items = new[]
{
new { product = "Widget D", quantity = 25, price = 200.00, total = 5000.00 }
},
orderTotal = 5000.00
}
}
}
}
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Customer Orders Report</title>
<style>
body {
font-family: Helvetica, sans-serif;
margin: 40pt;
}
h1 {
color: #1e40af;
}
.customer-section {
page-break-inside: avoid;
margin-bottom: 40pt;
border: 2pt solid #d1d5db;
border-radius: 5pt;
padding: 20pt;
}
.customer-header {
background-color: #eff6ff;
margin: -20pt -20pt 20pt -20pt;
padding: 15pt 20pt;
border-bottom: 2pt solid #2563eb;
}
.customer-name {
font-size: 18pt;
font-weight: bold;
color: #1e40af;
}
.customer-id {
font-size: 10pt;
color: #666;
}
.order {
margin: 20pt 0;
padding: 15pt;
background-color: #f9fafb;
border-left: 4pt solid #3b82f6;
}
.order-header {
display: table;
width: 100%;
margin-bottom: 10pt;
}
.order-number {
display: table-cell;
font-weight: bold;
font-size: 14pt;
}
.order-status {
display: table-cell;
text-align: right;
}
table {
width: 100%;
border-collapse: collapse;
margin: 10pt 0;
}
th {
background-color: #2563eb;
color: white;
padding: 8pt;
text-align: left;
font-size: 10pt;
}
td {
padding: 6pt 8pt;
border-bottom: 1pt solid #e5e7eb;
font-size: 10pt;
}
.summary-box {
background-color: #dbeafe;
padding: 10pt;
margin-top: 10pt;
border-radius: 3pt;
}
</style>
</head>
<body>
<h1>{{model.reportTitle}}</h1>
<p style="color: #666;">Generated: {{format(model.reportDate, 'MMMM dd, yyyy HH:mm')}}</p>
{{#each model.customers}}
<div class="customer-section">
<!-- Customer Header -->
<div class="customer-header">
<div class="customer-name">{{this.customerName}}</div>
<div class="customer-id">{{this.customerId}}</div>
<div style="font-size: 10pt; margin-top: 5pt;">
{{this.email}} | {{this.phone}}
</div>
</div>
<!-- Customer Summary -->
<div class="summary-box">
<strong>Customer Summary:</strong>
{{this.totalOrders}} orders | Total Spent: {{format(this.totalSpent, 'C0')}}
</div>
<!-- Orders -->
<h3>Orders</h3>
{{#each this.orders}}
<div class="order">
<div class="order-header">
<div class="order-number">{{this.orderNumber}}</div>
<div class="order-status">
Status:
<span style="color: {{if(this.status == 'Delivered', '#059669',
if(this.status == 'Shipped', '#f59e0b',
'#2563eb'))}};">
{{this.status}}
</span>
</div>
</div>
<p style="font-size: 10pt; color: #666;">
Order Date: {{format(this.orderDate, 'MMM dd, yyyy')}}
</p>
<!-- Order Items -->
<table>
<thead>
<tr>
<th>Product</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Price</th>
<th style="text-align: right;">Total</th>
</tr>
</thead>
<tbody>
{{#each this.items}}
<tr>
<td>{{this.product}}</td>
<td style="text-align: center;">{{this.quantity}}</td>
<td style="text-align: right;">{{format(this.price, 'C2')}}</td>
<td style="text-align: right;">{{format(this.total, 'C2')}}</td>
</tr>
{{/each}}
</tbody>
<tfoot>
<tr style="background-color: white; font-weight: bold;">
<td colspan="3" style="text-align: right;">Order Total:</td>
<td style="text-align: right;">{{format(this.orderTotal, 'C2')}}</td>
</tr>
</tfoot>
</table>
</div>
{{/each}}
</div>
{{/each}}
</body>
</html>
Pattern 3: Grouped Data with Subtotals
Group data by category and calculate subtotals.
C# Code:
var salesData = new[]
{
new { region = "North", salesperson = "Alice", amount = 50000 },
new { region = "North", salesperson = "Bob", amount = 45000 },
new { region = "South", salesperson = "Charlie", amount = 60000 },
new { region = "South", salesperson = "Diana", amount = 55000 },
new { region = "East", salesperson = "Eve", amount = 70000 },
new { region = "East", salesperson = "Frank", amount = 65000 }
};
// Group by region
var grouped = salesData
.GroupBy(s => s.region)
.Select(g => new
{
region = g.Key,
sales = g.Select(s => new { salesperson = s.salesperson, amount = s.amount }).ToArray(),
subtotal = g.Sum(s => s.amount),
salespeople = g.Count()
})
.ToArray();
doc.Params["model"] = new
{
title = "Sales by Region",
reportDate = DateTime.Now,
regions = grouped,
grandTotal = salesData.Sum(s => s.amount)
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Sales Report</title>
<style>
body {
font-family: Helvetica, sans-serif;
margin: 40pt;
}
h1 {
color: #1e40af;
}
.region {
margin-bottom: 30pt;
}
.region-header {
background-color: #2563eb;
color: white;
padding: 10pt;
font-size: 16pt;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
}
td {
padding: 8pt;
border-bottom: 1pt solid #e5e7eb;
}
.subtotal-row {
background-color: #eff6ff;
font-weight: bold;
}
.grandtotal-row {
background-color: #1e40af;
color: white;
font-size: 16pt;
font-weight: bold;
}
</style>
</head>
<body>
<h1>{{model.title}}</h1>
<p>Report Date: {{format(model.reportDate, 'MMMM dd, yyyy')}}</p>
{{#each model.regions}}
<div class="region">
<div class="region-header">
{{this.region}} Region ({{this.salespeople}} salespeople)
</div>
<table>
{{#each this.sales}}
<tr>
<td>{{this.salesperson}}</td>
<td style="text-align: right;">{{format(this.amount, 'C0')}}</td>
</tr>
{{/each}}
<tr class="subtotal-row">
<td>{{../region}} Subtotal</td>
<td style="text-align: right;">{{format(this.subtotal, 'C0')}}</td>
</tr>
</table>
</div>
{{/each}}
<!-- Grand Total -->
<table>
<tr class="grandtotal-row">
<td>GRAND TOTAL</td>
<td style="text-align: right;">{{format(model.grandTotal, 'C0')}}</td>
</tr>
</table>
</body>
</html>
Pattern 4: Conditional Page Breaks
Control page breaks based on data.
{{#each model.items}}
<div class="item" style="{{if(@index > 0 && calc(@index, '%', 5) == 0, 'page-break-before: always;', '')}}">
<!-- Content -->
<h3>{{this.title}}</h3>
<p>{{this.content}}</p>
</div>
{{/each}}
Or with explicit conditions:
{{#each model.sections}}
{{#if this.startNewPage}}
<div style="page-break-before: always;"></div>
{{/if}}
<div class="section">
<h2>{{this.title}}</h2>
<p>{{this.content}}</p>
</div>
{{/each}}
Pattern 5: Dynamic Column Layouts
Create responsive multi-column layouts based on data count.
C# Code:
doc.Params["model"] = new
{
products = productList,
columnCount = productList.Length <= 3 ? 1 : (productList.Length <= 6 ? 2 : 3)
};
Template:
<div style="display: table; width: 100%;">
{{#each model.products}}
<div style="display: table-cell; width: {{calc(100, '/', ../columnCount)}}%; padding: 10pt; vertical-align: top;">
<div style="border: 1pt solid #d1d5db; padding: 15pt;">
<h3>{{this.name}}</h3>
<p>{{format(this.price, 'C2')}}</p>
</div>
</div>
{{#if(calc(add(@index, 1), '%', ../columnCount))}}
<!-- Continue on same row -->
{{else}}
</div><div style="display: table; width: 100%;">
{{/if}}
{{/each}}
</div>
Pattern 6: Reusable Template Components
Create reusable styling configurations.
C# Code:
// Shared styling configuration
var brandStyle = new
{
colors = new
{
primary = "#2563eb",
secondary = "#3b82f6",
success = "#059669",
warning = "#f59e0b",
danger = "#dc2626"
},
fonts = new
{
heading = "24pt",
subheading = "18pt",
body = "11pt"
}
};
// Apply to multiple documents
doc.Params["brand"] = brandStyle;
doc.Params["data"] = actualData;
Template:
<style>
h1 {
font-size: {{brand.fonts.heading}};
color: {{brand.colors.primary}};
}
h2 {
font-size: {{brand.fonts.subheading}};
color: {{brand.colors.secondary}};
}
.success {
color: {{brand.colors.success}};
}
.warning {
color: {{brand.colors.warning}};
}
</style>
Pattern 7: Data-Driven Conditional Styles
Apply styles based on data values.
{{#each model.items}}
<div style="padding: 10pt;
background-color: {{if(this.priority == 'high', '#fee2e2',
if(this.priority == 'medium', '#fef3c7',
'#f3f4f6'))}};
border-left: 4pt solid {{if(this.priority == 'high', '#dc2626',
if(this.priority == 'medium', '#f59e0b',
'#9ca3af'))}};">
<h3>{{this.title}}</h3>
<p>Priority: {{upper(this.priority)}}</p>
</div>
{{/each}}
Pattern 8: Pagination with Index
Display items with page-aware numbering.
<table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{{#each model.items}}
<tr style="{{if(calc(@index, '%', 20) == 0 && @index > 0, 'page-break-before: always;', '')}}">
<td>{{add(@index, 1)}}</td>
<td>{{this.name}}</td>
<td>{{this.details}}</td>
</tr>
{{/each}}
</tbody>
</table>
Try It Yourself
Exercise 1: Multi-Level Grouping
Create a report that groups data by:
- Country → Region → City → Store
- Calculate subtotals at each level
- Show grand total at the end
Exercise 2: Dynamic Dashboard
Create a dashboard that:
- Shows different widgets based on user role
- Displays different chart types based on data type
- Adjusts layout based on widget count
- Includes conditional page breaks
Exercise 3: Complex Invoice
Create an invoice with:
- Multiple shipping addresses (if applicable)
- Line items with conditional discounts
- Volume-based pricing tiers
- Dynamic payment terms based on customer type
- Conditional late fees display
Common Pitfalls
❌ Deeply Nested Context Issues
{{#each level1}}
{{#each this.level2}}
{{#each this.level3}}
{{#each this.level4}}
<!-- Accessing parent data becomes error-prone -->
<p>{{../../../level1Property}}</p>
{{/each}}
{{/each}}
{{/each}}
{{/each}}
✅ Solution: Restructure data or use root parameters
// Option 1: Flatten data structure
// Option 2: Pass needed values at each level
doc.Params["companyName"] = "Tech Corp"; // Accessible from anywhere
❌ Complex Inline Conditionals
{{if(model.a && model.b || model.c && !model.d, if(model.e > 10, 'value1', 'value2'), 'value3')}}
✅ Solution: Calculate in C#
var displayValue = DetermineDisplayValue(model);
doc.Params["displayValue"] = displayValue;
❌ Repeating Calculations
{{#each items}}
<p>Total: {{format(calc(this.price, '*', this.quantity, '+', calc(this.price, '*', this.quantity, '*', 0.08)), 'C2')}}</p>
{{/each}}
✅ Solution: Pre-calculate in C#
var items = rawItems.Select(item => new
{
item.price,
item.quantity,
subtotal = item.price * item.quantity,
tax = item.price * item.quantity * 0.08,
total = (item.price * item.quantity) * 1.08
}).ToArray();
Next Steps
Now that you’ve mastered advanced patterns:
- Common Mistakes - Avoid common errors
- Styling PDFs - Advanced styling techniques
- Practical Applications - Real-world examples
Continue learning → Common Mistakes