Converting HTML to PDF in ASP.NET MVC using iTextSharp remains one of the most searched-for development challenges, with the original Stack Overflow question accumulating over 81,000 views. Developers attempting this integration discover that iTextSharp was not designed for browser-quality HTML rendering. The library's HTMLWorker class provides only basic HTML parsing that breaks with modern HTML5 and CSS3 content, leaving developers to either maintain simplified HTML templates or seek alternative approaches.
The Problem
iTextSharp, the .NET port of iText 5, is fundamentally a PDF manipulation library rather than an HTML rendering engine. When developers try to convert Razor views or HTML strings to PDF, they encounter significant limitations with the available parsing options.
The library offers two approaches for HTML conversion:
- HTMLWorker (deprecated): A simple parser that handles basic HTML tags but ignores most CSS styling
- XMLWorker: A more capable replacement that still lacks support for modern HTML5 elements and CSS3 features
Neither option renders HTML as a browser would. The gap between developer expectations and actual output is substantial. Developers expect their carefully styled Razor views to produce matching PDF output, but iTextSharp produces a rough approximation at best.
Error Messages and Symptoms
When using HTMLWorker in MVC:
iTextSharp.text.html.simpleparser.HTMLWorker is obsolete:
'Since iTextSharp 5.5.10 class is obsolete. Please use XMLWorker'
When attempting to parse modern HTML:
// This approach produces broken output for modern HTML
using (var stringReader = new StringReader(htmlContent))
{
HTMLWorker htmlWorker = new HTMLWorker(document);
htmlWorker.Parse(stringReader); // CSS ignored, HTML5 elements fail
}
With XMLWorker:
iTextSharp.tool.xml.exceptions.RuntimeWorkerException:
Invalid nested tag div found, expected closing tag tr.
Symptoms developers observe:
- Tables lose column widths and alignment
- CSS floats and positioning are ignored
- Background colors and images do not appear
- Web fonts display as system fallbacks
- Responsive layouts collapse into single columns
- Bootstrap or Tailwind styled content renders incorrectly
Who Is Affected
The 81,000+ views on the original Stack Overflow question represent a fraction of developers facing this challenge.
Framework Combinations:
- ASP.NET MVC 3, 4, 5 with .NET Framework
- ASP.NET Core MVC with iText 7
- Web Forms projects rendering HTML strings
Common Use Cases:
- Invoice generation from Razor templates
- Report exports from dashboard views
- Document generation from CMS content
- Receipt printing from e-commerce checkouts
- Certificate generation from user data
Industries Affected:
- Financial services (statements, reports)
- E-commerce (invoices, receipts)
- Healthcare (patient summaries)
- Legal (document automation)
- Education (certificates, transcripts)
Evidence from the Developer Community
Timeline
| Date | Event | Source |
|---|---|---|
| 2013-05-13 | Question posted asking how to convert HTML to PDF in MVC with iTextSharp | Stack Overflow |
| 2013-2015 | Multiple answers recommend HTMLWorker approach | Stack Overflow |
| 2015 | iTextSharp 5.5.10 deprecates HTMLWorker | iText Release Notes |
| 2015-2018 | Answers shift to XMLWorker and third-party wrappers | Stack Overflow |
| 2018-03-29 | Last significant answer activity | Stack Overflow |
| 2025 | Question continues receiving views, demonstrating ongoing demand | Stack Overflow |
Community Reports
"I am trying to convert HTML to PDF with iTextSharp in MVC Razor, but everything I have tried has not worked."
— Developer, Stack Overflow, May 2013
The top-voted answer (34 votes) does not show direct iTextSharp usage at all. Instead, it recommends a third-party wrapper library (MvcRazorToPdf) that abstracts the complexity, indicating the community recognized that raw iTextSharp HTML conversion was too difficult for practical use.
"You should check out RazorPDF which is using iText to generate the PDF, but in a friendlier way."
— Second-highest voted answer, acknowledging iTextSharp's complexity
These patterns reveal that even the community's best solutions involve wrapping iTextSharp rather than using it directly for HTML conversion.
Root Cause Analysis
iTextSharp was designed as a PDF object manipulation library. Its architecture excels at:
- Creating PDFs programmatically using a document object model
- Modifying existing PDF files
- Filling form fields
- Extracting text and metadata
- Merging and splitting PDF documents
HTML-to-PDF conversion requires a fundamentally different architecture:
| Requirement | Browser Engine | iTextSharp |
|---|---|---|
| HTML5 parsing | Full spec compliance | Basic tags only |
| CSS Box Model | Complete implementation | Partial |
| CSS3 (flexbox, grid) | Full support | Not supported |
| JavaScript execution | V8 engine | Not available |
| Web fonts | Automatic loading | Manual configuration |
| Layout calculation | Pixel-accurate | Approximate |
HTMLWorker was a convenience feature, never intended to match browser rendering. XMLWorker improved upon it but still approaches the problem as "parse HTML tags and approximate layout" rather than "render HTML exactly as a browser would."
The fundamental issue is architectural: accurate HTML rendering requires a browser engine, and iTextSharp does not include one.
Attempted Workarounds
Workaround 1: XMLWorker with Custom CSS
Approach: Replace HTMLWorker with XMLWorker and provide explicit CSS.
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
using System.IO;
public byte[] ConvertWithXmlWorker(string html)
{
using (var ms = new MemoryStream())
{
using (var document = new Document(PageSize.A4))
{
var writer = PdfWriter.GetInstance(document, ms);
document.Open();
using (var htmlStream = new MemoryStream(
System.Text.Encoding.UTF8.GetBytes(html)))
{
XMLWorkerHelper.GetInstance().ParseXHtml(
writer, document, htmlStream, null);
}
document.Close();
}
return ms.ToArray();
}
}
Limitations:
- HTML must be valid XHTML (self-closing tags required)
- CSS support remains incomplete
- No flexbox, grid, or modern layouts
- Images require absolute paths or base64 encoding
- External stylesheets need manual loading
Workaround 2: MvcRazorToPdf Wrapper
Approach: Use a third-party library that wraps iTextSharp with Razor view rendering.
// Using MvcRazorToPdf package
public ActionResult GeneratePdf()
{
var model = GetReportData();
return new PdfActionResult("ReportView", model);
}
Limitations:
- Still uses iTextSharp's limited HTML parsing underneath
- Additional dependency to maintain
- Limited CSS support persists
- Project may be abandoned or incompatible with newer .NET versions
Workaround 3: Render to Simplified HTML
Approach: Create separate, simplified templates for PDF output.
// Maintain two versions of each template
public ActionResult ViewReport()
{
return View("Report", model); // Full HTML/CSS
}
public ActionResult DownloadReport()
{
return View("Report_Pdf", model); // Simplified for iTextSharp
}
Limitations:
- Double maintenance burden
- Templates can drift out of sync
- Significant development overhead
- Cannot leverage existing web designs
A Different Approach: IronPDF
IronPDF takes a fundamentally different architectural approach. Rather than parsing HTML and approximating layout, it embeds a Chromium browser engine that renders HTML exactly as Chrome would, then converts the rendered output to PDF.
Why IronPDF Handles MVC HTML Differently
The architectural difference is significant:
- Actual Browser Engine: Chromium renders the HTML with full specification compliance
- Complete CSS3: Flexbox, Grid, Variables, and animations render correctly
- JavaScript Execution: Dynamic content, charting libraries, and frameworks work
- Web Fonts: Google Fonts and custom fonts load automatically
- Existing Templates: Razor views render without modification
When you convert HTML with IronPDF, you get PDF output that matches what you see in Chrome DevTools.
Code Example: Basic MVC Controller
using IronPdf;
using Microsoft.AspNetCore.Mvc;
public class ReportController : Controller
{
// Return PDF as file download
public IActionResult DownloadReport()
{
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #2c3e50; border-bottom: 2px solid #3498db; }
.content { line-height: 1.6; }
</style>
</head>
<body>
<h1>Monthly Report</h1>
<div class='content'>
<p>Generated: " + DateTime.Now.ToString("MMMM dd, yyyy") + @"</p>
</div>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
return File(pdf.BinaryData, "application/pdf", "report.pdf");
}
}
Code Example: Rendering Razor Views
using IronPdf;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
public class InvoiceController : Controller
{
private readonly ICompositeViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
public InvoiceController(
ICompositeViewEngine viewEngine,
ITempDataProvider tempDataProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
}
public async Task<IActionResult> DownloadInvoice(int id)
{
var invoice = await GetInvoice(id);
// Render Razor view to HTML string
string html = await RenderViewToString("InvoiceTemplate", invoice);
var renderer = new ChromePdfRenderer();
// Configure for print output
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.MarginLeft = 15;
renderer.RenderingOptions.MarginRight = 15;
renderer.RenderingOptions.PrintHtmlBackgrounds = true;
using var pdf = renderer.RenderHtmlAsPdf(html);
return File(pdf.BinaryData, "application/pdf", $"Invoice-{id}.pdf");
}
private async Task<string> RenderViewToString(string viewName, object model)
{
ViewData.Model = model;
using var writer = new StringWriter();
var viewResult = _viewEngine.FindView(ControllerContext, viewName, false);
var viewContext = new ViewContext(
ControllerContext,
viewResult.View,
ViewData,
new TempDataDictionary(ControllerContext.HttpContext, _tempDataProvider),
writer,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return writer.ToString();
}
private Task<Invoice> GetInvoice(int id)
{
// Database lookup
return Task.FromResult(new Invoice { Id = id });
}
}
Code Example: Modern CSS with Flexbox and Grid
public IActionResult DashboardPdf()
{
var renderer = new ChromePdfRenderer();
// Enable JavaScript for any dynamic content
renderer.RenderingOptions.EnableJavaScript = true;
renderer.RenderingOptions.RenderDelay = 200;
string html = @"
<!DOCTYPE html>
<html>
<head>
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'
rel='stylesheet'>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', sans-serif;
padding: 40px;
background: #f5f7fa;
}
h1 {
font-size: 28px;
color: #1a202c;
margin-bottom: 24px;
}
.dashboard {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.card-title {
font-size: 14px;
color: #718096;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.card-value {
font-size: 36px;
font-weight: 700;
color: #2d3748;
}
.card-change {
font-size: 14px;
color: #48bb78;
}
.card-change.negative { color: #f56565; }
@media print {
body { background: white; padding: 20px; }
.dashboard { grid-template-columns: repeat(2, 1fr); }
}
</style>
</head>
<body>
<h1>Q4 2025 Performance Dashboard</h1>
<div class='dashboard'>
<div class='card'>
<div class='card-header'>
<span class='card-title'>Revenue</span>
</div>
<div class='card-value'>$2.4M</div>
<div class='card-change'>+12.5% vs Q3</div>
</div>
<div class='card'>
<div class='card-header'>
<span class='card-title'>Active Users</span>
</div>
<div class='card-value'>48,291</div>
<div class='card-change'>+8.3% vs Q3</div>
</div>
<div class='card'>
<div class='card-header'>
<span class='card-title'>Conversion Rate</span>
</div>
<div class='card-value'>3.2%</div>
<div class='card-change negative'>-0.4% vs Q3</div>
</div>
</div>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
return File(pdf.BinaryData, "application/pdf", "dashboard.pdf");
}
Code Example: Inline Display vs Download
public class DocumentController : Controller
{
// Display PDF inline in browser
public IActionResult ViewDocument(int id)
{
var renderer = new ChromePdfRenderer();
string html = GetDocumentHtml(id);
using var pdf = renderer.RenderHtmlAsPdf(html);
// Set Content-Disposition to inline for browser viewing
return File(pdf.BinaryData, "application/pdf");
}
// Force download with filename
public IActionResult DownloadDocument(int id)
{
var renderer = new ChromePdfRenderer();
string html = GetDocumentHtml(id);
using var pdf = renderer.RenderHtmlAsPdf(html);
// Third parameter triggers download
return File(pdf.BinaryData, "application/pdf", $"document-{id}.pdf");
}
private string GetDocumentHtml(int id)
{
// Load from database or generate from template
return "<html><body><h1>Document Content</h1></body></html>";
}
}
Key points about this code:
-
pdf.BinaryDatareturns a byte array, eliminating stream position issues common with iTextSharp - Google Fonts load automatically without manual configuration
- CSS Grid layout works exactly as it does in Chrome
- Print media queries are respected for PDF output
- Same Razor views work for web display and PDF generation
API Reference
For more details on the methods used:
Migration Considerations
Licensing
IronPDF is commercial software with per-developer licensing. A free trial is available for evaluation before purchase. See licensing details for pricing information.
API Differences
The conceptual shift from iTextSharp to IronPDF:
| iTextSharp Approach | IronPDF Approach |
|---|---|
| Parse HTML tags into PDF elements | Render HTML in browser engine |
| Manual CSS handling | Automatic CSS processing |
| Limited tag support | Full HTML5 support |
| Build document programmatically | Design in HTML/CSS |
Migration Steps
- Install IronPDF via NuGet:
Install-Package IronPdf - Remove iTextSharp HTMLWorker or XMLWorker code
- Replace with ChromePdfRenderer.RenderHtmlAsPdf()
- Keep existing HTML templates unchanged
- Test output against browser rendering
What You Gain
- Browser-accurate PDF output from any HTML/CSS
- JavaScript execution for dynamic content
- Full CSS3 support including flexbox and grid
- Web fonts load automatically
- Existing Razor views work without modification
What to Consider
- Chromium binaries increase deployment size (~100-200 MB)
- Different pricing model than iText
- Requires .NET Framework 4.6.2+ or .NET Core 2.0+
Conclusion
Converting HTML to PDF in ASP.NET MVC with iTextSharp requires working around fundamental limitations in the library's HTML parsing capabilities. The community has developed wrapper libraries and simplified template approaches, but none provide browser-quality rendering. For projects where Razor views must render accurately as PDFs, a Chromium-based approach produces output that matches what developers see in their browsers.
Jacob Mellor is CTO at Iron Software and leads the technical development of IronPDF.
References
- Stack Overflow: Convert HTML to PDF in MVC with iTextSharp in MVC Razor{:rel="nofollow"} - 81K+ views, the original question this article addresses
- Stack Overflow: How to convert HTML to PDF using iTextSharp{:rel="nofollow"} - 309K+ views on related HTML-to-PDF conversion
- MvcRazorToPdf GitHub Repository{:rel="nofollow"} - Third-party wrapper recommended in top answer
For the latest IronPDF documentation and tutorials, visit ironpdf.com.
Top comments (0)