DEV Community

IronSoftware
IronSoftware

Posted on

Convert HTML to PDF in MVC with iTextSharp (Issue Fixed)

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:

  1. HTMLWorker (deprecated): A simple parser that handles basic HTML tags but ignores most CSS styling
  2. 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'
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

With XMLWorker:

iTextSharp.tool.xml.exceptions.RuntimeWorkerException:
Invalid nested tag div found, expected closing tag tr.
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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 });
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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>";
    }
}
Enter fullscreen mode Exit fullscreen mode

Key points about this code:

  • pdf.BinaryData returns 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

  1. Install IronPDF via NuGet: Install-Package IronPdf
  2. Remove iTextSharp HTMLWorker or XMLWorker code
  3. Replace with ChromePdfRenderer.RenderHtmlAsPdf()
  4. Keep existing HTML templates unchanged
  5. 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

  1. Stack Overflow: Convert HTML to PDF in MVC with iTextSharp in MVC Razor{:rel="nofollow"} - 81K+ views, the original question this article addresses
  2. Stack Overflow: How to convert HTML to PDF using iTextSharp{:rel="nofollow"} - 309K+ views on related HTML-to-PDF conversion
  3. 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)