Migrating from Fluid (Templating) to IronPDF
Fluid is a .NET library that implements the Liquid templating language, offering developers a flexible way to render dynamic templates and separate content from presentation logic. While Fluid is effective at generating dynamic text outputs, it does not directly support PDF generation—developers must integrate an additional PDF library to convert the HTML output to PDF documents. This two-library approach introduces complexity that many development teams seek to eliminate.
This guide provides a complete migration path from Fluid (templating) with external PDF libraries to IronPDF, with step-by-step instructions, code comparisons, and practical examples for professional .NET developers evaluating this transition.
Why Migrate from Fluid (Templating) to IronPDF
Fluid is a solid Liquid-based templating engine, but using it for PDF generation introduces significant complexity:
Two-Library Dependency: Fluid only generates HTML—you need a separate PDF library (wkhtmltopdf, PuppeteerSharp, etc.) to create PDFs, doubling your dependencies and maintenance burden.
Integration Complexity: Coordinating two libraries means managing two sets of configurations, error handling, and updates. When something breaks, debugging becomes more challenging.
Liquid Syntax Learning Curve: Developers must learn Liquid templating syntax ({{ }}, {% %}) when C# already has powerful string handling capabilities built-in.
Limited PDF Control: Your PDF output quality depends on whichever PDF library you choose to pair with Fluid, not on a dedicated rendering engine.
Debugging Challenges: Errors can occur at either the templating or PDF generation stage, making troubleshooting harder than with a single integrated solution.
Thread Safety Concerns: TemplateContext is not thread-safe and requires careful management in concurrent applications.
IronPDF vs Fluid (Templating): Feature Comparison
Understanding the architectural differences helps technical decision-makers evaluate the migration investment:
| Aspect | Fluid + PDF Library | IronPDF |
|---|---|---|
| Dependencies | 2+ packages (Fluid + PDF library) | Single package |
| Templating | Liquid syntax ({{ }}) | C# string interpolation or Razor |
| PDF Generation | External library required | Built-in Chromium engine |
| CSS Support | Depends on PDF library | Full CSS3 with Flexbox/Grid |
| JavaScript | Depends on PDF library | Full JavaScript support |
| Thread Safety | TemplateContext not thread-safe | ChromePdfRenderer is thread-safe |
| Learning Curve | Liquid + PDF library API | HTML/CSS (web standards) |
| Error Handling | Two error sources | Single error source |
Quick Start: Fluid to IronPDF Migration
The migration can begin immediately with these foundational steps.
Step 1: Replace NuGet Packages
Remove Fluid and any external PDF library:
# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet # or whatever PDF library you used
dotnet remove package PuppeteerSharp # if used# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet # or whatever PDF library you used
dotnet remove package PuppeteerSharp # if usedInstall IronPDF:
# Install IronPDF (all-in-one solution)
dotnet add package IronPdf# Install IronPDF (all-in-one solution)
dotnet add package IronPdfStep 2: Update Namespaces
Replace Fluid namespaces with IronPdf:
// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering; // For RenderingOptions// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering; // For RenderingOptionsStep 3: Initialize License
Add license initialization at application startup:
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";Code Migration Examples
Basic HTML to PDF
The most fundamental operation reveals the key difference between these approaches.
Fluid Approach:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
var context = new TemplateContext();
context.SetValue("name", "World");
var html = await template.RenderAsync(context);
// Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html);
}
}// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
var context = new TemplateContext();
context.SetValue("name", "World");
var html = await template.RenderAsync(context);
// Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html);
}
}IronPDF Approach:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World!</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World!</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}Fluid requires creating a FluidParser, parsing the template string, creating a TemplateContext, calling SetValue() for each variable, rendering asynchronously to get HTML, and then writing to a file—which still isn't a PDF. The comment in the code explicitly states "Fluid only generates HTML - you'd need another library to convert to PDF."
IronPDF eliminates this complexity: create a renderer, call RenderHtmlAsPdf(), and save directly to PDF. No intermediate HTML files, no additional libraries.
For advanced HTML-to-PDF scenarios, see the HTML to PDF conversion guide.
Invoice Template with Dynamic Data
Document templates with multiple variables show the templating pattern differences clearly.
Fluid Approach:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>");
var context = new TemplateContext();
context.SetValue("invoiceNumber", "12345");
context.SetValue("date", DateTime.Now.ToShortDateString());
context.SetValue("customer", "John Doe");
context.SetValue("total", 599.99);
var html = await template.RenderAsync(context);
// Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html);
}
}// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>");
var context = new TemplateContext();
context.SetValue("invoiceNumber", "12345");
context.SetValue("date", DateTime.Now.ToShortDateString());
context.SetValue("customer", "John Doe");
context.SetValue("total", 599.99);
var html = await template.RenderAsync(context);
// Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html);
}
}IronPDF Approach:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var invoiceNumber = "12345";
var date = DateTime.Now.ToShortDateString();
var customer = "John Doe";
var total = 599.99;
var html = $@"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {date}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var invoiceNumber = "12345";
var date = DateTime.Now.ToShortDateString();
var customer = "John Doe";
var total = 599.99;
var html = $@"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {date}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
}
}Fluid uses Liquid's {{variable}} syntax with context.SetValue() for each variable. The comment explicitly notes "Fluid outputs HTML - requires additional PDF library." IronPDF uses standard C# string interpolation ($"{variable}")—syntax developers already know—and outputs directly to PDF.
Explore the IronPDF tutorials for more document generation patterns.
Dynamic Data with Loops
Templates with collections and loops demonstrate control flow differences.
Fluid Approach:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>");
var context = new TemplateContext();
context.SetValue("title", "My List");
context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });
var html = await template.RenderAsync(context);
// Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html);
}
}// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>");
var context = new TemplateContext();
context.SetValue("title", "My List");
context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });
var html = await template.RenderAsync(context);
// Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html);
}
}IronPDF Approach:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var title = "My List";
var items = new[] { "Item 1", "Item 2", "Item 3" };
var html = $@"
<html><body>
<h1>{title}</h1>
<ul>";
foreach (var item in items)
{
html += $"<li>{item}</li>";
}
html += "</ul></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("template-output.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var title = "My List";
var items = new[] { "Item 1", "Item 2", "Item 3" };
var html = $@"
<html><body>
<h1>{title}</h1>
<ul>";
foreach (var item in items)
{
html += $"<li>{item}</li>";
}
html += "</ul></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("template-output.pdf");
}
}Fluid uses Liquid's {% for item in items %}...{% endfor %} syntax—a templating language developers must learn. The comment notes "Fluid generates HTML only - separate PDF conversion needed." IronPDF uses standard C# foreach loops—no new syntax to learn—and outputs directly to PDF.
Fluid API to IronPDF Mapping Reference
This mapping accelerates migration by showing direct API equivalents:
Core Class Mapping
| Fluid Class | IronPDF Equivalent | Notes |
|---|---|---|
FluidParser | N/A | Not needed—use C# strings |
FluidTemplate | N/A | Not needed |
TemplateContext | C# objects/strings | Pass data directly |
TemplateOptions | RenderingOptions | PDF output configuration |
FluidValue | Native C# types | No conversion needed |
| External PDF class | ChromePdfRenderer | Main rendering class |
Method Mapping
| Fluid Method | IronPDF Equivalent | Notes |
|---|---|---|
new FluidParser() | new ChromePdfRenderer() | Create renderer instead |
parser.Parse(source) | N/A | Not needed—HTML is a string |
template.RenderAsync(context) | renderer.RenderHtmlAsPdf(html) | Direct PDF rendering |
context.SetValue("key", value) | var key = value; | Use C# variables |
Liquid Syntax to C# Mapping
| Liquid Syntax | C# Equivalent | Notes |
|---|---|---|
{{ variable }} | $"{variable}" | String interpolation |
{% for item in items %} | foreach (var item in items) | C# loop |
{% if condition %} | if (condition) | C# conditional |
{{ x \| upcase }} | x.ToUpper() | C# method |
{{ x \| date: '%Y-%m-%d' }} | x.ToString("yyyy-MM-dd") | C# formatting |
{{ x \| number_with_precision: 2 }} | x.ToString("F2") | C# number formatting |
Common Migration Issues and Solutions
Issue 1: Liquid Syntax Conversion
Fluid: Uses {{ variable }} and {% control %} syntax.
Solution: Replace with C# string interpolation and control flow:
// Liquid: {{ name | upcase }}
// C#: $"{name.ToUpper()}"
// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }// Liquid: {{ name | upcase }}
// C#: $"{name.ToUpper()}"
// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }Issue 2: TemplateContext Variables
Fluid: Uses context.SetValue("key", value) to pass data.
Solution: Use standard C# variables:
// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);
// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);
// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";Issue 3: Thread Safety
Fluid: TemplateContext is not thread-safe, requiring careful management in concurrent applications.
Solution: ChromePdfRenderer is thread-safe and can be shared across threads:
// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public byte[] GeneratePdf(string html)
{
var pdf = _renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public byte[] GeneratePdf(string html)
{
var pdf = _renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}Issue 4: Two-Stage Error Handling
Fluid: Errors can occur at templating stage OR PDF generation stage.
Solution: IronPDF has a single error source:
try
{
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
// Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}");
}try
{
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
// Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}");
}Fluid Migration Checklist
Pre-Migration Tasks
Audit your codebase to identify all Fluid usage:
# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .
# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .
# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"Document all templates: file locations, variables used, loops and conditionals, and external PDF library configuration.
Code Update Tasks
- Remove Fluid.Core NuGet package
- Remove external PDF library package
- Install IronPdf NuGet package
- Update namespace imports from
FluidtoIronPdf - Convert
{{ variable }}to$"{variable}" - Convert
{% for item in collection %}to C#foreach - Convert
{% if condition %}to C#ifstatements - Convert Liquid filters to C# methods (e.g.,
| upcase→.ToUpper()) - Replace
FluidParserwithChromePdfRenderer - Replace
TemplateContext.SetValue()with direct C# variables - Remove external PDF library calls
- Add IronPDF license initialization at startup
Post-Migration Testing
After migration, verify these aspects:
- Verify PDF output matches expectations
- Test all template variations render correctly
- Check images and styling display properly
- Validate page breaks occur correctly
- Test with various data sizes
- Performance testing vs Fluid + external library
- Test thread safety in concurrent scenarios
Cleanup Tasks
- Delete
.liquidtemplate files (if no longer needed) - Remove Fluid-related helper code
- Update documentation
- Clean up unused dependencies
Key Benefits of Migrating to IronPDF
Moving from Fluid (templating) with external PDF libraries to IronPDF provides several critical advantages:
Single Package Solution: Eliminate the two-library dependency. IronPDF handles both templating (via HTML/CSS) and PDF generation in one package.
No New Syntax to Learn: Use standard C# string interpolation and control flow instead of learning Liquid templating syntax.
Thread-Safe Rendering: ChromePdfRenderer is thread-safe, unlike TemplateContext, simplifying concurrent PDF generation.
Chromium Rendering Engine: Industry-standard rendering ensures full CSS3 support including Flexbox and Grid, plus complete JavaScript execution.
Single Error Source: Debugging becomes simpler with only one library to troubleshoot instead of coordinating between templating and PDF generation stages.
Active Development: As .NET 10 and C# 14 adoption increases through 2026, IronPDF's regular updates ensure compatibility with current and future .NET versions.






