Choosing a Document Generation Library for .NET 8
.NET 8 introduced runtime changes that break compatibility with many traditional PDF libraries. System.Drawing.Common is deprecated on non-Windows platforms, Native AOT imposes new constraints on reflection-heavy libraries, and the container-first deployment model exposes dependency issues that didn't surface on Windows development machines. Libraries that worked in .NET Framework or even .NET 6 may fail with NU1202, DllNotFoundException, or PlatformNotSupportedException errors in .NET 8.
This article documents which PDF libraries work with .NET 8, shows the specific errors you'll encounter with incompatible libraries, and provides deployment configurations for Docker and Azure Functions.
Quickstart: Generate PDF from HTML in .NET 8
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
var pdf = renderer.RenderHtmlAsPdf(@"
<html>
<head><style>
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
h1 { color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th { background: #2563eb; color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style></head>
<body>
<h1>Q4 Report</h1>
<table>
<tr><th>Metric</th><th>Value</th><th>Change</th></tr>
<tr><td>Revenue</td><td>$1.2M</td><td>+12%</td></tr>
<tr><td>Users</td><td>45,230</td><td>+23%</td></tr>
</table>
</body></html>");
pdf.SaveAs("report.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
var pdf = renderer.RenderHtmlAsPdf(@"
<html>
<head><style>
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
h1 { color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th { background: #2563eb; color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style></head>
<body>
<h1>Q4 Report</h1>
<table>
<tr><th>Metric</th><th>Value</th><th>Change</th></tr>
<tr><td>Revenue</td><td>$1.2M</td><td>+12%</td></tr>
<tr><td>Users</td><td>45,230</td><td>+23%</td></tr>
</table>
</body></html>");
pdf.SaveAs("report.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4
Dim pdf = renderer.RenderHtmlAsPdf("
<html>
<head><style>
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
h1 { color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th { background: #2563eb; color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style></head>
<body>
<h1>Q4 Report</h1>
<table>
<tr><th>Metric</th><th>Value</th><th>Change</th></tr>
<tr><td>Revenue</td><td>$1.2M</td><td>+12%</td></tr>
<tr><td>Users</td><td>45,230</td><td>+23%</td></tr>
</table>
</body></html>")
pdf.SaveAs("report.pdf")Install via NuGet: Install-Package IronPdf. Targets net8.0 without additional configuration. Deploys to Windows, Linux, macOS, Docker, and Azure without platform-specific setup.
Why Do Older PDF Libraries Fail in .NET 8?
iTextSharp — Not Compatible with .NET 8
iTextSharp (the original .NET port of iText) hasn't been updated since 2018. It targets .NET Framework only:
error NU1202: Package iTextSharp 5.5.13 is not compatible with net8.0The migration path is iText 7, which has a different API and requires either AGPL compliance (open-source your entire application) or commercial licensing. Pricing is not published — third-party data suggests $15,000–$210,000/year.
wkhtmltopdf Wrappers — DLL Loading Failures on .NET 8
C# wrappers around wkhtmltopdf (DinkToPdf, Rotativa, NReco.PdfGenerator) fail with platform-specific errors that are unique to .NET 8's deployment model:
DinkToPdf — native library not found:
System.DllNotFoundException: Unable to load DLL 'libwkhtmltox'Rotativa.AspNetCore — requires manual binary path configuration that conflicts with .NET 8's container-first deployment:
// This pattern requires deploying wkhtmltopdf binaries alongside your app
// and breaks in Docker containers without explicit path configuration
RotativaConfiguration.Setup(env.WebRootPath, "wkhtmltopdf");// This pattern requires deploying wkhtmltopdf binaries alongside your app
// and breaks in Docker containers without explicit path configuration
RotativaConfiguration.Setup(env.WebRootPath, "wkhtmltopdf");' This pattern requires deploying wkhtmltopdf binaries alongside your app
' and breaks in Docker containers without explicit path configuration
RotativaConfiguration.Setup(env.WebRootPath, "wkhtmltopdf")TuesPechkin / NReco — architecture mismatch:
System.BadImageFormatException: Could not load file or assemblyThese errors reflect a deeper problem: wkhtmltopdf was archived in July 2024 with unpatched CVEs. The native binary architecture predates .NET 8's platform detection improvements. There is no fix — only migration to a different library.
PdfSharp — System.Drawing.Common Failures on Linux
PdfSharp depends on System.Drawing.Common, which Microsoft deprecated for non-Windows platforms in .NET 6. In .NET 8, this produces runtime failures when deploying to Linux or Docker:
using PdfSharp.Pdf;
using PdfSharp.Drawing;
var document = new PdfDocument();
// PlatformNotSupportedException on Linux:
// System.Drawing.Common is not supported on this platformusing PdfSharp.Pdf;
using PdfSharp.Drawing;
var document = new PdfDocument();
// PlatformNotSupportedException on Linux:
// System.Drawing.Common is not supported on this platformImports PdfSharp.Pdf
Imports PdfSharp.Drawing
Dim document As New PdfDocument()
' PlatformNotSupportedException on Linux:
' System.Drawing.Common is not supported on this platformPdfSharp 6.x has been working to remove this dependency, but cross-platform support remains incomplete as of early 2026. For Windows-only deployments, PdfSharp works with .NET 8. For Linux, Docker, or cloud deployments, it's unreliable.
What PDF Libraries Work with .NET 8?
IronPDF — Full .NET 8 Support, Cross-Platform
IronPDF targets net8.0 natively with embedded Chromium rendering. No System.Drawing.Common dependency, no native binary management, no platform-specific configuration.
Minimal API integration (the primary .NET 8 pattern):
using IronPdf;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/invoice/{id}/pdf", async (int id, InvoiceService service) =>
{
var invoice = await service.GetInvoiceAsync(id);
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(invoice.ToHtml());
return Results.File(pdf.BinaryData, "application/pdf", $"invoice-{id}.pdf");
});
app.Run();using IronPdf;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/invoice/{id}/pdf", async (int id, InvoiceService service) =>
{
var invoice = await service.GetInvoiceAsync(id);
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(invoice.ToHtml());
return Results.File(pdf.BinaryData, "application/pdf", $"invoice-{id}.pdf");
});
app.Run();Imports IronPdf
Dim builder = WebApplication.CreateBuilder(args)
Dim app = builder.Build()
app.MapGet("/invoice/{id}/pdf", Async Function(id As Integer, service As InvoiceService)
Dim invoice = Await service.GetInvoiceAsync(id)
Dim renderer = New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(invoice.ToHtml())
Return Results.File(pdf.BinaryData, "application/pdf", $"invoice-{id}.pdf")
End Function)
app.Run()For teams using the controller pattern, the same code works inside a controller action — RenderHtmlAsPdf returns a PdfDocument you can convert to bytes with .BinaryData.
Licensing: Perpetual license starting at $749 (Lite — 1 developer, 1 project). Professional: $1,499 (10 developers). Enterprise: $2,999 (unlimited). Published at ironpdf.com.
QuestPDF — .NET 8 Compatible, No HTML
QuestPDF works with .NET 8 without platform dependencies. Its fluent C# API builds documents programmatically — no HTML parser, no CSS engine, no browser engine:
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
QuestPDF.Settings.License = LicenseType.Community; // Free under $1M revenue
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Content().Column(col =>
{
col.Item().Text("Q4 Report").FontSize(24).Bold();
col.Item().Table(table =>
{
table.ColumnsDefinition(c => { c.RelativeColumn(2); c.RelativeColumn(1); c.RelativeColumn(1); });
table.Header(h =>
{
h.Cell().Text("Metric").Bold();
h.Cell().Text("Value").Bold();
h.Cell().Text("Change").Bold();
});
table.Cell().Text("Revenue"); table.Cell().Text("$1.2M"); table.Cell().Text("+12%");
table.Cell().Text("Users"); table.Cell().Text("45,230"); table.Cell().Text("+23%");
});
});
});
}).GeneratePdf("report.pdf");using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
QuestPDF.Settings.License = LicenseType.Community; // Free under $1M revenue
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Content().Column(col =>
{
col.Item().Text("Q4 Report").FontSize(24).Bold();
col.Item().Table(table =>
{
table.ColumnsDefinition(c => { c.RelativeColumn(2); c.RelativeColumn(1); c.RelativeColumn(1); });
table.Header(h =>
{
h.Cell().Text("Metric").Bold();
h.Cell().Text("Value").Bold();
h.Cell().Text("Change").Bold();
});
table.Cell().Text("Revenue"); table.Cell().Text("$1.2M"); table.Cell().Text("+12%");
table.Cell().Text("Users"); table.Cell().Text("45,230"); table.Cell().Text("+23%");
});
});
});
}).GeneratePdf("report.pdf");Imports QuestPDF.Fluent
Imports QuestPDF.Infrastructure
QuestPDF.Settings.License = LicenseType.Community ' Free under $1M revenue
Document.Create(Sub(container)
container.Page(Sub(page)
page.Size(PageSizes.A4)
page.Margin(2, Unit.Centimetre)
page.Content().Column(Sub(col)
col.Item().Text("Q4 Report").FontSize(24).Bold()
col.Item().Table(Sub(table)
table.ColumnsDefinition(Sub(c)
c.RelativeColumn(2)
c.RelativeColumn(1)
c.RelativeColumn(1)
End Sub)
table.Header(Sub(h)
h.Cell().Text("Metric").Bold()
h.Cell().Text("Value").Bold()
h.Cell().Text("Change").Bold()
End Sub)
table.Cell().Text("Revenue")
table.Cell().Text("$1.2M")
table.Cell().Text("+12%")
table.Cell().Text("Users")
table.Cell().Text("45,230")
table.Cell().Text("+23%")
End Sub)
End Sub)
End Sub)
End Sub).GeneratePdf("report.pdf")QuestPDF doesn't convert HTML. If your workflow uses HTML templates (Razor views, dashboard exports, web content archiving), you need a different library. The Community License covers businesses under $1M annual revenue; above that, commercial licensing is required.
iText 7 — .NET 8 Compatible, AGPL Licensed
iText 7 (the successor to iTextSharp) supports .NET 8. The pdfHTML add-on provides HTML-to-PDF conversion with a custom parser — not a browser engine, so modern CSS features (Flexbox, Grid) don't render correctly.
Licensing: AGPL for open source use, commercial licensing (subscription, pricing not published) for proprietary applications. iText transitioned to subscription-based commercial licensing in 2024.
Docker Deployment
Standard Debian Image (Recommended)
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# IronPDF works without additional apt-get installs
# Chromium dependencies are handled internally
COPY --from=build /app/publish .
ENV DOTNET_RUNNING_IN_CONTAINER=true
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.dll"]Alpine Image (Smaller Footprint)
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
# Alpine requires explicit Chromium dependencies
RUN apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]The Debian image requires zero additional packages. The Alpine image is smaller but needs explicit Chromium library installation. For most deployments, the standard Debian image is simpler and more reliable.
Azure Functions (Isolated Worker)
.NET 8 Azure Functions use the isolated worker model. IronPDF works with this model for on-demand PDF generation:
using IronPdf;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace MyApp.Functions;
public class PdfFunctions
{
[Function("GenerateInvoice")]
public async Task<HttpResponseData> GenerateInvoice(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var invoice = await req.ReadFromJsonAsync<InvoiceRequest>();
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(BuildInvoiceHtml(invoice));
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/pdf");
response.Headers.Add("Content-Disposition",
$"attachment; filename=invoice-{invoice.Id}.pdf");
await response.Body.WriteAsync(pdf.BinaryData);
return response;
}
private string BuildInvoiceHtml(InvoiceRequest invoice)
{
return $@"<html><body>
<h1>Invoice #{invoice.Id}</h1>
<p>Amount: ${invoice.Amount:F2}</p>
</body></html>";
}
}
public record InvoiceRequest(string Id, decimal Amount);using IronPdf;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace MyApp.Functions;
public class PdfFunctions
{
[Function("GenerateInvoice")]
public async Task<HttpResponseData> GenerateInvoice(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var invoice = await req.ReadFromJsonAsync<InvoiceRequest>();
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(BuildInvoiceHtml(invoice));
var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/pdf");
response.Headers.Add("Content-Disposition",
$"attachment; filename=invoice-{invoice.Id}.pdf");
await response.Body.WriteAsync(pdf.BinaryData);
return response;
}
private string BuildInvoiceHtml(InvoiceRequest invoice)
{
return $@"<html><body>
<h1>Invoice #{invoice.Id}</h1>
<p>Amount: ${invoice.Amount:F2}</p>
</body></html>";
}
}
public record InvoiceRequest(string Id, decimal Amount);Imports IronPdf
Imports Microsoft.Azure.Functions.Worker
Imports Microsoft.Azure.Functions.Worker.Http
Namespace MyApp.Functions
Public Class PdfFunctions
<Function("GenerateInvoice")>
Public Async Function GenerateInvoice(
<HttpTrigger(AuthorizationLevel.Function, "post")> req As HttpRequestData) As Task(Of HttpResponseData)
Dim invoice = Await req.ReadFromJsonAsync(Of InvoiceRequest)()
Dim renderer = New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(BuildInvoiceHtml(invoice))
Dim response = req.CreateResponse(System.Net.HttpStatusCode.OK)
response.Headers.Add("Content-Type", "application/pdf")
response.Headers.Add("Content-Disposition", $"attachment; filename=invoice-{invoice.Id}.pdf")
Await response.Body.WriteAsync(pdf.BinaryData)
Return response
End Function
Private Function BuildInvoiceHtml(invoice As InvoiceRequest) As String
Return $"<html><body>
<h1>Invoice #{invoice.Id}</h1>
<p>Amount: ${invoice.Amount:F2}</p>
</body></html>"
End Function
End Class
Public Class InvoiceRequest
Public Property Id As String
Public Property Amount As Decimal
Public Sub New(id As String, amount As Decimal)
Me.Id = id
Me.Amount = amount
End Sub
End Class
End NamespaceDeployment consideration: IronPDF's Chromium binary adds ~200MB to the deployment package. Azure Functions consumption plan has a 1.5GB deployment size limit — verify your total package size. For Premium or Dedicated plans, this limit doesn't apply. Cold-start latency for the first PDF generation is 2–5 seconds as Chromium initializes.
Native AOT Compatibility
.NET 8 expanded Native AOT support, but PDF libraries face fundamental constraints. Chromium-based libraries (IronPDF, Puppeteer Sharp) cannot compile AOT because they embed or spawn browser processes. iText 7 uses extensive reflection that conflicts with trimming.
Current status with <PublishAot>true</PublishAot>:
| Library | AOT Status | Reason |
|---|---|---|
| IronPDF | Not compatible | Embeds Chromium runtime |
| iText 7 | Not compatible | Heavy reflection, dynamic code generation |
| QuestPDF | Partial (with TrimMode=partial) | Some reflection usage |
| PdfSharp | Not compatible | System.Drawing.Common dependency |
If Native AOT is a hard requirement, QuestPDF with TrimMode=partial is the closest option — but only for programmatic document generation, not HTML conversion. For HTML-to-PDF scenarios, AOT is not currently viable with any library.
Licensing Comparison
| Library | Model | Cost | .NET 8 Support |
|---|---|---|---|
| IronPDF | Perpetual | $749 (Lite) / $1,499 (Pro) / $2,999 (Enterprise) | Full |
| iText 7 | AGPL or Subscription | Not published ($15K–$210K/yr est.) | Full |
| QuestPDF | Community / Commercial | Free <$1M revenue, then commercial | Full |
| PdfSharp | MIT (free) | $0 | Windows only (Linux incomplete) |
| Aspose.PDF | Per-developer | ~$999+ | Full (Linux memory issues) |
.NET 9 Forward Compatibility
.NET 9 (released November 2025) continues the patterns established in .NET 8. Libraries that work with .NET 8 generally work with .NET 9 without changes. The key .NET 9 additions relevant to PDF generation are improved ARM64 performance (benefits Chromium-based rendering on Apple Silicon and AWS Graviton) and continued Native AOT improvements (though the fundamental constraints for PDF libraries remain).
If you're targeting .NET 9 or planning to upgrade, selecting a library that fully supports .NET 8 cross-platform deployment is the safest path. Libraries with platform-specific issues (PdfSharp on Linux, Aspose with libgdiplus) are unlikely to resolve those issues in .NET 9 since the underlying dependency deprecation is permanent.
Migration Guide
From iTextSharp to IronPDF
// Before (iTextSharp — doesn't compile against net8.0)
using iTextSharp.text;
using iTextSharp.text.pdf;
var doc = new Document();
PdfWriter.GetInstance(doc, new FileStream("output.pdf", FileMode.Create));
doc.Open();
doc.Add(new Paragraph("Hello World"));
doc.Close();
// After (IronPDF — targets net8.0)
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<p>Hello World</p>");
pdf.SaveAs("output.pdf");// Before (iTextSharp — doesn't compile against net8.0)
using iTextSharp.text;
using iTextSharp.text.pdf;
var doc = new Document();
PdfWriter.GetInstance(doc, new FileStream("output.pdf", FileMode.Create));
doc.Open();
doc.Add(new Paragraph("Hello World"));
doc.Close();
// After (IronPDF — targets net8.0)
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<p>Hello World</p>");
pdf.SaveAs("output.pdf");Imports iTextSharp.text
Imports iTextSharp.text.pdf
Imports IronPdf
Imports System.IO
' Before (iTextSharp — doesn't compile against net8.0)
Dim doc As New Document()
PdfWriter.GetInstance(doc, New FileStream("output.pdf", FileMode.Create))
doc.Open()
doc.Add(New Paragraph("Hello World"))
doc.Close()
' After (IronPDF — targets net8.0)
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf("<p>Hello World</p>")
pdf.SaveAs("output.pdf")From wkhtmltopdf Wrappers to IronPDF
// Before (DinkToPdf — DllNotFoundException on .NET 8)
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument
{
Objects = { new ObjectSettings { HtmlContent = html } }
};
var bytes = converter.Convert(doc);
// After (IronPDF — native .NET 8 support)
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
var bytes = pdf.BinaryData;// Before (DinkToPdf — DllNotFoundException on .NET 8)
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument
{
Objects = { new ObjectSettings { HtmlContent = html } }
};
var bytes = converter.Convert(doc);
// After (IronPDF — native .NET 8 support)
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
var bytes = pdf.BinaryData;Imports IronPdf
' Before (DinkToPdf — DllNotFoundException on .NET 8)
Dim converter = New SynchronizedConverter(New PdfTools())
Dim doc = New HtmlToPdfDocument With {
.Objects = {New ObjectSettings With {.HtmlContent = html}}
}
Dim bytes = converter.Convert(doc)
' After (IronPDF — native .NET 8 support)
Dim renderer = New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(html)
bytes = pdf.BinaryDataThe API surface is simpler in both cases. The primary migration cost is testing your existing HTML templates against the new renderer to verify output matches expectations.
The Recommendation
For .NET 8 projects requiring HTML-to-PDF conversion: IronPDF provides embedded Chromium rendering that works cross-platform without configuration. It handles Docker deployment, Azure Functions, and Linux containers out of the box.
For .NET 8 projects building documents programmatically from data: QuestPDF's fluent API is well-designed and .NET 8 compatible, with the Community License covering most startups and small teams.
For PDF manipulation (merge, split, forms) on Windows: PdfSharp remains viable if your deployment target is Windows-only.
Avoid iTextSharp (not compatible), wkhtmltopdf wrappers (archived, CVEs), and any library depending on System.Drawing.Common for cross-platform deployment.