Migrating from Fluid (Templating) to IronPDF
Fluid is a .NET library (shipped on NuGet as Fluid.Core, authored by Sebastien Ros, MIT-licensed) that implements the Liquid templating language, offering developers a flexible way to render dynamic templates and separate content from presentation logic. Fluid is effective at generating dynamic text outputs, but 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
Install IronPDF:
# Install IronPDF (all-in-one solution)
dotnet add package IronPdf
Step 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 RenderingOptionsImports Fluid
Imports Fluid.Values
Imports SomeExternalPdfLibrary
Imports IronPdf
Imports 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
Imports Fluid
Imports System.IO
Imports System.Threading.Tasks
Module Program
Async Function Main() As Task
Dim parser As New FluidParser()
Dim template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>")
Dim context As New TemplateContext()
context.SetValue("name", "World")
Dim html = Await template.RenderAsync(context)
' Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html)
End Function
End ModuleIronPDF 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");
}
}Imports IronPdf
Imports System
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim html = "<html><body><h1>Hello World!</h1></body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
End Sub
End ClassFluid 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
Imports Fluid
Imports System
Imports System.IO
Imports System.Threading.Tasks
Module Program
Async Function Main() As Task
Dim parser = New FluidParser()
Dim template = parser.Parse("
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>")
Dim context = New TemplateContext()
context.SetValue("invoiceNumber", "12345")
context.SetValue("date", DateTime.Now.ToShortDateString())
context.SetValue("customer", "John Doe")
context.SetValue("total", 599.99)
Dim html = Await template.RenderAsync(context)
' Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html)
End Function
End ModuleIronPDF 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
Imports IronPdf
Imports System
Module Program
Sub Main()
Dim renderer As New ChromePdfRenderer()
Dim invoiceNumber As String = "12345"
Dim [date] As String = DateTime.Now.ToShortDateString()
Dim customer As String = "John Doe"
Dim total As Double = 599.99
Dim html As String = $"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {[date]}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("invoice.pdf")
End Sub
End ModuleFluid 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
Imports Fluid
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks
Module Program
Async Function Main() As Task
Dim parser = New FluidParser()
Dim template = parser.Parse("
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>")
Dim context = New TemplateContext()
context.SetValue("title", "My List")
context.SetValue("items", New String() { "Item 1", "Item 2", "Item 3" })
Dim html = Await template.RenderAsync(context)
' Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html)
End Function
End ModuleIronPDF 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
Imports IronPdf
Imports System
Imports System.Collections.Generic
Module Program
Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim title = "My List"
Dim items = {"Item 1", "Item 2", "Item 3"}
Dim html = $"
<html><body>
<h1>{title}</h1>
<ul>"
For Each item In items
html += $"<li>{item}</li>"
Next
html += "</ul></body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("template-output.pdf")
End Sub
End ModuleFluid 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 |
|---|---|
FluidParser | N/A |
FluidTemplate | N/A |
TemplateContext | C# objects/strings |
TemplateOptions | RenderingOptions |
FluidValue | Native C# types |
| External PDF class | ChromePdfRenderer |
Method Mapping
| Fluid Method | IronPDF Equivalent |
|---|---|
new FluidParser() | new ChromePdfRenderer() |
parser.Parse(source) | N/A |
template.RenderAsync(context) | renderer.RenderHtmlAsPdf(html) |
context.SetValue("key", value) | var key = value; |
Liquid Syntax to C# Mapping
| Liquid Syntax | C# Equivalent |
|---|---|
{{ variable }} | $"{variable}" |
{% for item in items %} | foreach (var item in items) |
{% if condition %} | if (condition) |
{{ x | upcase }} | x.ToUpper() |
{{ x | date: '%Y-%m-%d' }} | x.ToString("yyyy-MM-dd") |
{{ x | number_with_precision: 2 }} | x.ToString("F2") |
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}"; }netIssue 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)
Dim context As New TemplateContext()
context.SetValue("customer", customerName)
' After (IronPDF)
Dim customer = customerName
Dim 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 Shared ReadOnly _renderer As New ChromePdfRenderer()
Public Function GeneratePdf(html As String) As Byte()
Dim pdf = _renderer.RenderHtmlAsPdf(html)
Return pdf.BinaryData
End FunctionIssue 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
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
Catch ex As Exception
' Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}")
End TryFluid 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 "{{"
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: Chromium-based rendering supports modern CSS (including Flexbox and Grid) and JavaScript execution.
Single Error Source: Debugging becomes simpler with only one library to troubleshoot instead of coordinating between templating and PDF generation stages.
Broad .NET Coverage: IronPDF supports .NET Framework 4.6.2+, .NET Core 2.0+, .NET Standard 2.0, and .NET 5 through .NET 10.

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.