Developers using iTextSharp or iText to return PDF files to the browser in ASP.NET MVC frequently encounter "The file is damaged and could not be repaired" errors. With over 256,000 views on Stack Overflow, this is one of the most common iText integration issues. The problem stems from incorrect stream handling that corrupts the PDF output. This article documents the pattern that causes corruption and shows how to properly return PDF files in MVC, including modern .NET Core and .NET 6/7/8 approaches.
The Problem
When generating a PDF with iTextSharp and returning it through an MVC FileResult, the PDF often arrives corrupted in the browser. Adobe Reader or other PDF viewers report the file as damaged despite the code appearing correct.
The issue occurs because:
- The MemoryStream must be properly positioned before returning
- The PdfWriter must be closed before the stream is read
- The Document must be closed to finalize the PDF structure
- The stream disposal timing must be managed carefully
Error Messages and Symptoms
In Adobe Reader:
There was an error opening this document. The file is damaged and could not be repaired.
In Chrome's PDF viewer:
Failed to load PDF document
The PDF file may:
- Open as a blank page
- Display only partial content
- Show as 0 bytes
- Trigger download instead of inline display
Who Is Affected
This issue impacts any ASP.NET application generating PDFs with iTextSharp or iText 7:
Framework Versions:
- .NET Framework 4.x with MVC 3, 4, 5
- .NET Core 2.x, 3.x
- .NET 5, 6, 7, 8 with ASP.NET Core MVC or Razor Pages
- Blazor Server applications returning file downloads
Common Scenarios:
- Invoice generation systems returning PDFs for download
- Report generation with inline browser preview
- Document preview features in web applications
- PDF merge operations returning combined documents
- E-commerce order confirmations
- Ticket and receipt generation
The Famous Stack Overflow Question:
The question "How to return PDF to browser in MVC?" has accumulated over 256,000 views since it was first posted in October 2009. This makes it one of the most-viewed questions about PDF generation in .NET. The answers have evolved over the years as developers discovered the correct patterns and as .NET itself has evolved through multiple major versions.
Evidence from the Developer Community
Scale of the Problem
| Metric | Value |
|---|---|
| Stack Overflow Views | 256,990+ |
| Upvotes | 140 |
| Answers | 11 |
| Years Active | 2009-2019 |
Community Reports
"Running this code does open Acrobat but I get an error message 'The file is damaged and could not be repaired'"
— Developer, Stack Overflow, October 2009"I've tried every variation of this code and keep getting corrupted PDFs in the browser."
— Developer, Stack Overflow, 2015
Root Cause Analysis
The corruption occurs due to stream lifecycle issues. Consider this common (broken) pattern:
// BROKEN CODE - causes corruption
public FileStreamResult Pdf()
{
MemoryStream m = new MemoryStream();
Document document = new Document();
PdfWriter.GetInstance(document, m);
document.Open();
document.Add(new Paragraph("Hello World"));
document.Close();
return File(m, "application/pdf", "test.pdf");
// m.Position is at the end, so nothing is returned
}
The problems:
-
m.Positionis at the end of the stream after writing - MVC reads nothing - The stream might be disposed before MVC can read it
- The Document.Close() call is essential but often mishandled
Correct Implementation with iTextSharp
.NET Framework MVC 5
The correct pattern requires resetting the stream position or using ToArray():
public FileContentResult Pdf()
{
using (var ms = new MemoryStream())
{
var document = new Document(PageSize.A4);
var writer = PdfWriter.GetInstance(document, ms);
document.Open();
document.Add(new Paragraph("Hello World"));
document.Close();
// Convert to byte array - handles position automatically
return File(ms.ToArray(), "application/pdf", "test.pdf");
}
}
Key differences:
- Use
ms.ToArray()instead of returning the stream directly - Ensure
document.Close()is called before accessing the stream - Use
FileContentResultwith byte array instead ofFileStreamResult
.NET Core / .NET 6+ with iText 7
For modern .NET applications using iText 7:
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
public class ReportsController : Controller
{
public IActionResult GeneratePdf()
{
using var ms = new MemoryStream();
using (var writer = new PdfWriter(ms))
{
// Prevent writer from closing the stream
writer.SetCloseStream(false);
using var pdf = new PdfDocument(writer);
using var document = new Document(pdf);
document.Add(new Paragraph("Hello World"));
}
// Reset position before returning
ms.Position = 0;
return File(ms.ToArray(), "application/pdf", "report.pdf");
}
}
Async Controller Example
For high-traffic applications, use async patterns:
public async Task<IActionResult> GeneratePdfAsync(int reportId)
{
var reportData = await _reportService.GetReportDataAsync(reportId);
using var ms = new MemoryStream();
using (var writer = new PdfWriter(ms))
{
writer.SetCloseStream(false);
using var pdf = new PdfDocument(writer);
using var document = new Document(pdf);
// Build document from data
document.Add(new Paragraph(reportData.Title));
foreach (var section in reportData.Sections)
{
document.Add(new Paragraph(section.Content));
}
}
return File(ms.ToArray(), "application/pdf", $"report-{reportId}.pdf");
}
Content-Disposition Header Options
Control whether the PDF opens inline or downloads:
public IActionResult ViewPdfInline()
{
var pdfBytes = GeneratePdfBytes();
// Inline viewing - PDF opens in browser's PDF viewer
Response.Headers.Add("Content-Disposition", "inline; filename=report.pdf");
return File(pdfBytes, "application/pdf");
}
public IActionResult DownloadPdf()
{
var pdfBytes = GeneratePdfBytes();
// Attachment - triggers download dialog
return File(pdfBytes, "application/pdf", "report.pdf");
}
Common Stream Handling Mistakes
These patterns cause corruption:
// WRONG: Stream position at end
public IActionResult BrokenExample1()
{
var ms = new MemoryStream();
// ... write to stream ...
return File(ms, "application/pdf"); // Position is at end!
}
// WRONG: Stream disposed before read
public IActionResult BrokenExample2()
{
using (var ms = new MemoryStream())
{
// ... write to stream ...
return File(ms, "application/pdf"); // Stream disposed on return!
}
}
// WRONG: Using statement closes writer, which closes stream
public IActionResult BrokenExample3()
{
var ms = new MemoryStream();
using (var writer = new PdfWriter(ms)) // Closes ms when disposed!
{
// ...
}
return File(ms.ToArray(), "application/pdf"); // Stream already closed!
}
Troubleshooting Checklist
When encountering "file is damaged" errors, check these common issues:
Stream Position Issues
- [ ] Is the stream position at 0 before returning? Use
ms.Position = 0orms.ToArray() - [ ] Are you using
ToArray()to get bytes? This automatically handles position - [ ] Is
SetCloseStream(false)called on PdfWriter? (iText 7)
Document Lifecycle Issues
- [ ] Is
document.Close()called before reading the stream? - [ ] Is the PdfWriter properly disposed?
- [ ] Are you not disposing the MemoryStream too early?
Response Configuration Issues
- [ ] Is the content type exactly
"application/pdf"? - [ ] Is the filename included in the File() call?
- [ ] For inline display, is Content-Disposition header set correctly?
Memory Stream Best Practices
// CORRECT: Create byte array before stream disposal
public IActionResult CorrectPattern()
{
byte[] pdfBytes;
using (var ms = new MemoryStream())
{
// Generate PDF into stream
GeneratePdf(ms);
// Get bytes while stream is still open
pdfBytes = ms.ToArray();
}
// Stream is disposed, but we have the bytes
return File(pdfBytes, "application/pdf", "output.pdf");
}
A Simpler Approach: IronPDF
IronPDF's API is designed for common scenarios like returning PDFs in MVC, handling the stream management internally.
Why IronPDF Avoids This Problem
IronPDF provides:
- Direct byte array output - no stream position issues
- Built-in methods for common output scenarios
- Response streaming helpers for ASP.NET
Code Example
using IronPdf;
using Microsoft.AspNetCore.Mvc;
public class ReportsController : Controller
{
public FileContentResult GeneratePdf()
{
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Hello World</h1>
<p>Generated at: " + DateTime.Now + @"</p>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
// BinaryData is a byte[] - no stream position issues
return File(pdf.BinaryData, "application/pdf", "report.pdf");
}
public FileContentResult GenerateInvoice(int invoiceId)
{
var invoice = GetInvoice(invoiceId);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
string html = GenerateInvoiceHtml(invoice);
using var pdf = renderer.RenderHtmlAsPdf(html);
// Inline display in browser
Response.Headers.Add("Content-Disposition", "inline; filename=invoice.pdf");
return File(pdf.BinaryData, "application/pdf");
}
public IActionResult StreamPdfToResponse()
{
var renderer = new ChromePdfRenderer();
string html = "<html><body><h1>Streamed PDF</h1></body></html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
// Stream directly to response
return new FileStreamResult(
new MemoryStream(pdf.BinaryData),
"application/pdf"
);
}
private Invoice GetInvoice(int id) => /* database lookup */;
private string GenerateInvoiceHtml(Invoice invoice)
{
return $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
.header {{ border-bottom: 2px solid #333; padding-bottom: 20px; }}
table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
th {{ background-color: #f5f5f5; }}
.total {{ font-size: 1.3em; font-weight: bold; text-align: right; margin-top: 20px; }}
</style>
</head>
<body>
<div class='header'>
<h1>Invoice #{invoice.Number}</h1>
<p>Date: {invoice.Date:yyyy-MM-dd}</p>
</div>
<table>
<thead>
<tr><th>Item</th><th>Quantity</th><th>Price</th></tr>
</thead>
<tbody>
{GenerateLineItemRows(invoice.Items)}
</tbody>
</table>
<div class='total'>Total: ${invoice.Total:F2}</div>
</body>
</html>";
}
private string GenerateLineItemRows(IEnumerable<LineItem> items)
{
return string.Join("", items.Select(i =>
$"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"));
}
}
Key points about this code:
-
pdf.BinaryDatareturnsbyte[]- no stream position management needed - Works with both
FileContentResultandFileStreamResult - No risk of "damaged file" errors from incorrect stream handling
- Same code works for download or inline display
API Reference
For more details on web integration:
Migration Considerations
Licensing
- IronPDF is commercial software with perpetual licensing
- Free trial available for evaluation
- Licensing information
API Differences
- iTextSharp: Document → PdfWriter → Stream → FileResult
- IronPDF: Renderer → PDF → BinaryData → FileResult
What You Gain
- No stream position bugs
- No "file is damaged" errors
- Simpler code with fewer opportunities for mistakes
What to Consider
- Different API surface
- HTML-based rendering instead of object-based
- Commercial licensing required
Conclusion
The "file is damaged" error when returning iTextSharp PDFs in MVC stems from incorrect stream handling that is easy to get wrong. The correct iTextSharp pattern requires careful attention to stream position and document lifecycle. IronPDF's API returns byte arrays directly, eliminating this category of bugs entirely.
Jacob Mellor originally built IronPDF and leads Iron Software's technical vision.
References
- Stack Overflow: How to return PDF to browser in MVC?{:rel="nofollow"} - 256K+ views
For the latest IronPDF documentation and tutorials, visit ironpdf.com.
Top comments (0)