Skip to main content

How to Convert HTML to PDF in C# and .NET Using DinkToPdf

· 17 min read
Michał Szymanowski
Michał Szymanowski
PDFBolt Co-Founder

Guide to generating PDF from HTML using DinkToPdf in C#/.NET

If you need a reliable solution to convert HTML to PDF in C# and .NET, this comprehensive guide walks you through using DinkToPdf, a powerful .NET wrapper for the popular wkhtmltopdf library, to generate high-quality PDFs from HTML content. Whether you're creating invoices, reports, or any document requiring pixel-perfect PDF output, DinkToPdf offers an efficient way to transform your HTML and CSS into professional PDFs with minimal setup. This step-by-step tutorial will help you implement a complete HTML to PDF conversion system in your .NET application.

What is DinkToPdf?

DinkToPdf is a lightweight yet powerful .NET library that provides a convenient wrapper around wkhtmltopdf, a well-established HTML to PDF conversion tool. As a cross-platform solution, it allows developers to seamlessly generate PDFs from HTML content in .NET applications without external dependencies or browser installations.

Unlike browser-based solutions, DinkToPdf operates using the WebKit rendering engine, providing consistent rendering results across different environments.

Why Choose DinkToPdf for PDF Generation?

DinkToPdf provides a practical solution for many HTML to PDF conversion needs in .NET environments:

  • Lightweight Implementation: Requires minimal setup without the need for a full browser engine.
  • Fast Rendering: Offers relatively quick conversion with low resource usage.
  • Basic HTML/CSS Support: Supports standard HTML/CSS, but lacks full compatibility with modern CSS.
  • Simple Integration: Easily integrates into .NET applications via native bindings.
  • Open-Source Solution: Freely available on GitHub with community support (though development activity is limited).

Limitations of DinkToPdf

Despite its advantages, DinkToPdf has several important limitations:

  • Native Dependencies: Requires manual setup of wkhtmltopdf binaries appropriate for each operating system, which can be challenging to manage.
  • Limited CSS Support: Struggles with modern layouts like flexbox and grid.
  • JavaScript Limitations: Has restricted support for JavaScript execution, which may affect dynamic content rendering.
  • Minimal Maintenance: Limited ongoing development activity.
  • Memory Usage: Can be resource-intensive with complex documents.

Step-by-Step Guide: Creating a PDF with DinkToPdf

Step 1: Preparing Your Development Environment

Before generating PDFs with DinkToPdf in your .NET application, make sure your development environment is properly configured with the following prerequisites:

  1. Your preferred .NET development environment - Visual Studio, Visual Studio Code, or Rider.
  2. .NET 8.0 or .NET 9.0 - Download from the official Microsoft .NET website.

Verify your .NET setup by running this command in your terminal:

dotnet --version

Step 2: Create a New .NET Project

Start by creating a new .NET Console Application for PDF generation with DinkToPdf:

mkdir HtmlToPdfWithDinkToPdf
cd HtmlToPdfWithDinkToPdf
dotnet new console

This creates a basic console application structure with a Program.cs file.

Step 3: Organize Your Project Directory

For better maintainability, set up a clear directory structure:

HtmlToPdfWithDinkToPdf/

├── Program.cs # Application entry point
├── NativeLibraryHelper.cs # Helper for loading wkhtmltopdf native libraries

├── Templates/ # HTML templates
│ └── Invoice.hbs # Invoice template file

├── Output/ # Generated PDFs storage
│ └── (PDF files)

├── Models/ # Data models
│ └── InvoiceModel.cs # Invoice data structure

├── libwkhtmltox/ # Native wkhtmltopdf libraries
│ └── libwkhtmltox.dll # (or .so/.dylib depending on platform)

└── Services/ # Service classes
├── TemplateService.cs # Template rendering service
└── PdfService.cs # PDF generation service

Step 4: Install Required NuGet Packages

Add these essential packages to your project:

  • Install DinkToPdf for HTML to PDF conversion:
dotnet add package DinkToPdf

Install Handlebars.NET for templating:

dotnet add package Handlebars.Net
info

Handlebars.Net is a lightweight templating engine for .NET that implements the popular Handlebars syntax in C#. It provides a simple yet powerful way to separate your HTML/presentation logic from your application code. With Handlebars.Net, you can create templates with placeholders like {{PropertyName}}, conditionals, and loops to dynamically populate templates with data.

Step 5: Download and Set Up wkhtmltopdf

We need to download and integrate the wkhtmltopdf native libraries directly into our project.

  1. Download wkhtmltopdf:
  1. Extract the Library Files:
  • For Windows:
    • Run the installer.
    • After installation, find the wkhtmltox.dll file (typically in C:\Program Files\wkhtmltopdf\bin\).
  • For macOS or Linux:
    • Extract the libwkhtmltox.dylib (macOS) or libwkhtmltox.so (Linux) file.
  1. Copy to Your Project:
  • Create a libwkhtmltox folder in your project root.
  • Copy the extracted library file to this folder.
  • Rename it to libwkhtmltox.dll (Windows), libwkhtmltox.so (Linux), or libwkhtmltox.dylib (macOS).
  1. Configure Your Project File:
  • Open your .csproj file.
  • Add the following item group to ensure the library is copied to the output directory:
<ItemGroup>
<None Include="libwkhtmltox\**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Step 6: Create an Invoice Data Model

Let's build a model class for our invoice data.

Create InvoiceModel.cs in the Models directory:

InvoiceModel.cs
namespace HtmlToPdfWithDinkToPdf.Models
{
public class InvoiceModel
{
public required string InvoiceNumber { get; set; }
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public required string CompanyName { get; set; }
public required string CompanyAddress { get; set; }
public required string CompanyEmail { get; set; }
public required string CompanyPhone { get; set; }
public required string CompanyLogo { get; set; }
public required string ClientName { get; set; }
public required string ClientAddress { get; set; }
public required string ClientEmail { get; set; }
public decimal SubTotal => Items.Sum(item => item.Total);
public decimal TaxRate { get; set; }
public decimal TaxAmount => Math.Round(SubTotal * (TaxRate / 100), 2);
public decimal Total => SubTotal + TaxAmount;
public string Currency { get; set; } = "$";
public List<InvoiceItem> Items { get; set; } = new List<InvoiceItem>();
public required string Notes { get; set; }
}

public class InvoiceItem
{
public required string Description { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}
}

Step 7: Create a Handlebars Template

Now, let's create an HTML template using Handlebars syntax.

Create a file named Invoice.hbs in the Templates directory:

Invoice.hbs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice {{InvoiceNumber}}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 10px;
font-size: 18px;
}

.invoice-container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 30px;
}

.invoice-header {
width: 100%;
margin-bottom: 30px;
border-bottom: 2px solid #1FB7BF;
padding-bottom: 20px;
line-height: 1.4;
}

.header-table {
width: 100%;
border-collapse: collapse;
}

.invoice-title {
font-size: 38px;
font-weight: bold;
color: #159ca3;
margin-bottom: 10px;
}

.client-info-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}

.client-info-table td {
vertical-align: top;
padding: 5px;
line-height: 1.4;
}

.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}

.items-table th {
background-color: #312F30;
color: white;
padding: 12px;
text-align: center;
border: 1px solid #555;
}

.items-table td {
padding: 12px;
border: 1px solid #ccc;
}

.text-right {
text-align: right;
}

.items-table th.text-right {
text-align: right;
}

.items-table tr:nth-child(even) {
background-color: #eaf5f4;
}

.summary-table {
width: 50%;
margin-left: auto;
border-collapse: collapse;
}

.summary-table td {
padding: 12px;
border: 1px solid #ccc;
}

.summary-table td:last-child {
text-align: right;
}

.summary-table tr.total {
font-weight: bold;
font-size: 16px;
background-color: #eaf5f4;
}

.notes {
margin-top: 40px;
border-top: 1px solid #ddd;
padding-top: 20px;
}

.footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #ddd;
color: #777;
font-size: 15px;
}

.section-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
color: #159ca3;
}
</style>
</head>
<body>
<div class="invoice-container">
<!-- Header -->
<div class="invoice-header">
<table class="header-table">
<tr>
<td width="60%">
<div class="invoice-title">INVOICE</div>
<div>{{CompanyName}}</div>
<div>{{CompanyAddress}}</div>
<div>{{CompanyEmail}}</div>
<div>{{CompanyPhone}}</div>
</td>
<td width="40%" class="text-right">
{{#if CompanyLogo}}
<img src="{{CompanyLogo}}" alt="Company Logo" height="80">
{{/if}}
</td>
</tr>
</table>
</div>

<!-- Client info and invoice details -->
<table class="client-info-table">
<tr>
<td width="50%">
<div class="section-title">Bill To:</div>
<div>{{ClientName}}</div>
<div>{{ClientAddress}}</div>
<div>{{ClientEmail}}</div>
</td>
<td width="50%" class="text-right">
<div><strong>Invoice Number:</strong> {{InvoiceNumber}}</div>
<div><strong>Invoice Date:</strong> {{formatDate InvoiceDate}}</div>
<div><strong>Due Date:</strong> {{formatDate DueDate}}</div>
</td>
</tr>
</table>

<!-- Items -->
<table class="items-table">
<thead>
<tr>
<th width="50%">Description</th>
<th width="15%">Quantity</th>
<th width="15%">Unit Price</th>
<th width="20%">Total</th>
</tr>
</thead>
<tbody>
{{#each Items}}
<tr>
<td>{{Description}}</td>
<td class="text-right">{{Quantity}}</td>
<td class="text-right">{{formatCurrency ../Currency UnitPrice}}</td>
<td class="text-right">{{formatCurrency ../Currency Total}}</td>
</tr>
{{/each}}
</tbody>
</table>

<!-- Summary -->
<table class="summary-table">
<tr>
<td>Subtotal</td>
<td>{{formatCurrency Currency SubTotal}}</td>
</tr>
<tr>
<td>Tax ({{TaxRate}}%)</td>
<td>{{formatCurrency Currency TaxAmount}}</td>
</tr>
<tr class="total">
<td>TOTAL</td>
<td>{{formatCurrency Currency Total}}</td>
</tr>
</table>

<!-- Notes -->
<div class="notes">
<div class="section-title">Notes</div>
<p>{{Notes}}</p>
</div>

<!-- Footer -->
<div class="footer">
Thank you for your business!
</div>
</div>
</body>
</html>

Step 8: Create a Template Service with Handlebars.NET

Now, let's build a service that handles HTML templating using Handlebars.NET.

Create a file named TemplateService.cs in the Services directory:

TemplateService.cs
using HandlebarsDotNet;

namespace HtmlToPdfWithDinkToPdf.Services
{
public class TemplateService
{
private readonly IHandlebars _handlebars;

public TemplateService()
{
// Initialize Handlebars
_handlebars = Handlebars.Create();

// Register custom helpers for formatting
_handlebars.RegisterHelper("formatDate", (context, arguments) =>
{
if (arguments.Length > 0 && arguments[0] is DateTime date)
{
// Use English (US) culture for date formatting
return date.ToString("MMMM dd, yyyy", new System.Globalization.CultureInfo("en-US"));
}

return string.Empty;
});

_handlebars.RegisterHelper("formatCurrency", (context, arguments) =>
{
if (arguments.Length > 1 && arguments[1] is decimal amount)
{
string currency = arguments[0].ToString();
return $"{currency}{amount:0.00}";
}

return string.Empty;
});
}


public string RenderTemplate<T>(string templatePath, T model)
{
try
{
// Read the template content
string templateContent = File.ReadAllText(templatePath);

// Compile the template
var template = _handlebars.Compile(templateContent);
if (template is null)
{
throw new InvalidOperationException("Failed to compile template");
}

// Render the template with the provided model
string result = template(model);

return result;
}
catch (Exception ex)
{
Console.WriteLine($"Template rendering error: {ex.Message}");
throw;
}
}
}
}

Step 8: Create a Native Library Helper for DinkToPdf

Let's create a helper class to load the wkhtmltopdf native libraries:

NativeLibraryHelper.cs
using DinkToPdf;
using DinkToPdf.Contracts;
using System.Runtime.InteropServices;

namespace HtmlToPdfWithDinkToPdf
{
public static class NativeLibraryHelper
{
// Import Windows library loading function
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);

// NOTE: For Linux or macOS, uncomment the appropriate import and update the code accordingly
/*
[DllImport("libdl.so.2")]
private static extern IntPtr dlopen(string fileName, int flags);

[DllImport("libdl.dylib")]
private static extern IntPtr dlopen_macos(string fileName, int flags);
*/


// Initializes DinkToPdf by loading the necessary native wkhtmltopdf library
public static IConverter InitializeDinkToPdf()
{
// Define path to the DLL - replace with your actual path if different
string libraryPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"libwkhtmltox",
"libwkhtmltox.dll");

if (!File.Exists(libraryPath))
{
throw new FileNotFoundException($"Native library not found at: {libraryPath}");
}

// Load the library
LoadLibrary(libraryPath);

// Create and return the converter
return new SynchronizedConverter(new PdfTools());
}
}
}
Troubleshooting wkhtmltopdf Library Loading

If you encounter the error "Unable to load DLL 'libwkhtmltox' or one of its dependencies", try these steps:

  1. Verify you have the correct file name: libwkhtmltox.dll (not wkhtmltox.dll).
  2. Check if the file exists in the expected locations.
  3. Install the Visual C++ Redistributable.
  4. Try placing the DLL directly in your application's output directory (bin/Debug/net8.0/).
  5. Run the program with administrator privileges.

Step 9: Create a PDF Service

Let's build a service that handles PDF generation using DinkToPdf.

Create a file named PdfService.cs in the Services directory:

PdfService.cs
using DinkToPdf;
using DinkToPdf.Contracts;
using HtmlToPdfWithDinkToPdf.Models;

namespace HtmlToPdfWithDinkToPdf.Services
{
public class PdfService
{
private readonly IConverter _converter;
private readonly TemplateService _templateService;

public PdfService(IConverter converter, TemplateService templateService)
{
_converter = converter;
_templateService = templateService;
}

public string GenerateInvoicePdf(InvoiceModel invoice, string templatePath, string outputPath)
{
try
{
// Render the HTML template with data
string html = _templateService.RenderTemplate(templatePath, invoice);

// Save the processed HTML for debugging (optional)
string? directory = Path.GetDirectoryName(outputPath);
if (directory == null)
{
directory = ".";
}

string htmlOutputPath = Path.Combine(
directory,
$"{Path.GetFileNameWithoutExtension(outputPath)}.html"
);
File.WriteAllText(htmlOutputPath, html);

// Configure conversion settings
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4,
Margins = new MarginSettings { Top = 10, Right = 10, Bottom = 10, Left = 10 },
Out = outputPath
},
Objects = {
new ObjectSettings {
PagesCount = true,
HtmlContent = html,
WebSettings = { DefaultEncoding = "utf-8" },
FooterSettings = { FontSize = 9, Center = "Page [page] of [toPage]" },
}
}
};

// Generate the PDF
_converter.Convert(doc);

return outputPath;
}
catch (Exception ex)
{
Console.WriteLine($"Error generating PDF: {ex.Message}");
throw;
}
}
}
}

Step 10: Implement the Main Program

Finally, let's update the Program.cs file to bring everything together:

Program.cs
using DinkToPdf.Contracts;
using HtmlToPdfWithDinkToPdf.Models;
using HtmlToPdfWithDinkToPdf.Services;

namespace HtmlToPdfWithDinkToPdf
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting HTML to PDF conversion process with DinkToPdf");

try
{
// Define the output directory
string outputDir = Path.Combine(
Environment.CurrentDirectory,
"Output"
);

Directory.CreateDirectory(outputDir);
Directory.CreateDirectory("Templates");

// 1. Initialize DinkToPdf converter
IConverter converter = NativeLibraryHelper.InitializeDinkToPdf();
Console.WriteLine("DinkToPdf converter initialized");

// 2. Initialize Template and PDF services
var templateService = new TemplateService();
var pdfService = new PdfService(converter, templateService);
Console.WriteLine("PDF and Template services initialized");

// 3. Generate sample invoice data
var invoice = CreateSampleInvoice();
Console.WriteLine($"Created sample invoice with number: {invoice.InvoiceNumber}");

// 4. Define template and output paths
string templatePath = Path.Combine("Templates", "Invoice.hbs");
string pdfOutputPath = Path.Combine(outputDir, $"Invoice_{invoice.InvoiceNumber}.pdf");

// 5. Generate the PDF
Console.WriteLine("Generating PDF...");
pdfService.GenerateInvoicePdf(invoice, templatePath, pdfOutputPath);

Console.WriteLine("PDF generation process completed successfully!");
Console.WriteLine($"PDF saved to: {Path.GetFullPath(pdfOutputPath)}");
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
Console.ResetColor();
}
}

// Creates a sample invoice with test data
static InvoiceModel CreateSampleInvoice()
{
// Generate an invoice number with year and random identifier
Random random = new Random();
string invoiceNumber = $"INV-{DateTime.Now:yyyy}-{random.Next(100, 999)}";

return new InvoiceModel
{
InvoiceNumber = invoiceNumber,
InvoiceDate = DateTime.Now,
DueDate = DateTime.Now.AddDays(30),
CompanyName = "Miracle Stack Solutions",
CompanyAddress = "404 Dev Street, Cloud City, JS 40404",
CompanyEmail = "[email protected]",
CompanyPhone = "+1 (555) 123-4567",
CompanyLogo = "https://img.pdfbolt.com/logo-example-dark.png",
ClientName = "Null Industries",
ClientAddress = "16 Infinite Loop, Sandbox, CA 90210",
ClientEmail = "[email protected]",
Currency = "$",
TaxRate = 8.5m,
Notes = "Payment is due within 30 days. Please include the invoice number with your payment.",
Items = new List<InvoiceItem>
{
new InvoiceItem
{
Description = "Web Application Development",
Quantity = 15,
UnitPrice = 95.00m
},
new InvoiceItem
{
Description = "UI/UX Design Services",
Quantity = 20,
UnitPrice = 85.00m
},
new InvoiceItem
{
Description = "Server Configuration",
Quantity = 5,
UnitPrice = 110.00m
},
new InvoiceItem
{
Description = "Annual Hosting Fee",
Quantity = 1,
UnitPrice = 499.99m
},
new InvoiceItem
{
Description = "Technical Documentation Writing",
Quantity = 3,
UnitPrice = 75.00m
}
}
};
}
}
}

Step 11: Run the Application

Run the application to generate your PDF invoice:

dotnet run

When successful, the program will output the location of your generated PDF file in the Output directory. Opening this file will show your professionally formatted invoice.

Here's a preview of the generated invoice PDF: Invoice PDF generated using DinkToPdf in C#/.NET

Configuration Options in DinkToPdf

DinkToPdf offers extensive configuration options to fine-tune your PDF output:

SettingDescriptionExample
PaperSizeStandard paper sizePaperKind.A4, PaperKind.Letter etc.
ColorModeColor settingsColorMode.Color, ColorMode.Grayscale
OrientationPage orientationOrientation.Portrait, Orientation.Landscape
MarginsPage marginsnew MarginSettings { Top = 10, Bottom = 10 }
DPIResolution in DPI300
ImageDPIImage resolution300
ImageQualityJPEG compression100 (highest quality)
HeaderSettingsCustom headers{ FontSize = 9, Right = "Page [page]" }
FooterSettingsCustom footers{ Line = true, Center = "Confidential" }
LoadSettingsPage loading settings{ BlockLocalFileAccess = false }
WebSettingsWeb page settings{ DefaultEncoding = "utf-8" }

Here's an example with advanced settings:

var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4,
DPI = 300,
ImageDPI = 300,
ImageQuality = 100,
Margins = new MarginSettings {
Top = 20,
Right = 20,
Bottom = 20,
Left = 20
},
Out = outputPath
},
Objects = {
new ObjectSettings {
PagesCount = true,
HtmlContent = html,
WebSettings = {
DefaultEncoding = "utf-8",
EnableJavascript = true,
PrintMediaType = true
},
HeaderSettings = {
FontSize = 9,
Right = "Page [page] of [toPage]",
Line = true
},
FooterSettings = {
FontSize = 9,
Center = "Confidential Document",
Line = true
},
LoadSettings = { BlockLocalFileAccess = false }
}
}
};

Performance Optimization for DinkToPdf

When implementing DinkToPdf in production environments, consider these performance optimization techniques:

Converter Singleton Pattern

Since DinkToPdf's converter initialization is resource-intensive:

  • Create a single, application-wide converter instance.
  • Implement thread-safe access to this shared instance.
  • Consider using dependency injection to manage the converter lifetime.

Template Caching

For better performance with Handlebars.NET:

  • Cache compiled templates to avoid repeated processing.
  • Include proper error handling for cache misses.
  • Store templates in a dictionary keyed by file path.

Background Processing for Large Volumes

For applications generating many PDFs:

  • Implement a background service or queue.
  • Set appropriate timeouts for long-running operations.
  • Consider scaling horizontally with multiple workers for high-volume scenarios.
  • Implement file cleanup for temporary files.

C# Alternatives for PDF Generation

While DinkToPdf is excellent for many scenarios, consider these alternatives based on your specific requirements:

AlternativeBest ForLearn More
PuppeteerSharpComplex, dynamic content requiring Chrome/Chromium-based rendering with full JavaScript and modern CSS support.PuppeteerSharp HTML to PDF Guide
iText7Applications needing advanced PDF operations beyond conversion with low-level creation and manipulation capabilities.iText 7 HTML to PDF in C#/.NET
QuestPDFDevelopers who prefer a code-first approach with a fluent C# API rather than HTML templates.PDF Generation Using QuestPDF
HTML to PDF APIHigh-volume PDF generation with minimal server-side implementation through cloud-based conversion services.Convert HTML to PDF Using API

Skip Library Setup with HTML to PDF APIs

If you want to avoid the complexity of setting up and maintaining PDF libraries locally, consider using a cloud-based HTML to PDF API service like PDFBolt. These services eliminate the need to manage wkhtmltopdf dependencies, handle cross-platform compatibility issues, or worry about scaling for high-volume scenarios.

Conclusion

This guide has walked you through the process of converting HTML to PDF in C# and .NET using DinkToPdf with Handlebars.NET templates – from initial setup to generating professional PDF invoices that are ready for real-world use.

By combining DinkToPdf's efficient PDF conversion capabilities with Handlebars.NET's powerful templating features, you've learned how to create a flexible and maintainable system for generating dynamic PDF documents. While the manual setup of wkhtmltopdf libraries requires some initial effort, this approach gives you direct control over the rendering process and avoids potential compatibility issues with pre-packaged dependencies.

This solution offers practical benefits for many business scenarios – from generating invoices and reports to creating certificates and contracts. The separation of concerns between your data models and document templates makes your code more maintainable, while the performance optimization techniques discussed will help you scale this approach to meet enterprise-level document generation needs.

HTML converted, coffee consumed, developer satisfied. 🔥