IRONSOFTWAREHOME
USING IRONPDF

ASP.NET Core: Create PDF on the Fly with IronPDF

Curtis Chau
Curtis Chau
Updated: September 4, 2026

Generate professional PDF documents dynamically in ASP.NET Core by converting HTML content into polished PDFs and streaming them directly to the browser -- no disk storage required, no temporary files to manage.

When building modern web applications in ASP.NET Core, the ability to produce PDF documents on demand is a recurring requirement. Invoices need to download the moment a payment clears. Compliance reports must appear instantly when an auditor clicks "Export." Certificates should be ready before the user has time to wonder if something went wrong. IronPDF handles all of these scenarios through its Chromium-based PDF library, which converts HTML -- including CSS, JavaScript, and web fonts -- into pixel-accurate PDF output without writing anything to disk.

This guide covers everything you need to know: installing the library, generating invoices from HTML strings, streaming reports from Entity Framework data, applying page headers and security settings, and adopting the best practices that keep high-traffic ASP.NET applications performing well.

What Does Creating PDFs on the Fly Mean?

"On the fly" means the document is constructed in memory at the moment of the HTTP request and sent directly to the caller. No PDF file is written to the file system, no background job queues the work, and no cache stores the result between requests.

This approach matters for several reasons. First, cloud deployment targets -- Azure App Service, AWS Lambda, Docker containers -- often run in environments where the local file system is ephemeral or read-only. Generating a PDF to a temp folder and then reading it back is fragile in these environments. Second, avoiding disk writes reduces attack surface: there is no residual file a subsequent request could accidentally serve to the wrong user. Third, memory-only generation is typically faster because it eliminates two I/O operations (write and read) in the critical path.

IronPDF's ChromePdfRenderer exposes a .BinaryData property and a .Stream property on every generated document. Either can be passed directly to an ASP.NET Core FileResult, making streaming a one-liner in practice.

First Step:
arrow pointer

How Do You Install IronPDF in an ASP.NET Core Project?

Add the NuGet package through either the Package Manager Console or the .NET CLI:

PM > Install-Package IronPdf

Once the package is installed, set your license key at application startup -- typically in Program.cs before the first renderer is created:

using IronPdf;

// Place license activation before any IronPDF call
License.LicenseKey = "YOUR-LICENSE-KEY";

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();

// Register ChromePdfRenderer as a singleton so the Chromium engine
// is initialised once and reused across all requests.
builder.Services.AddSingleton<ChromePdfRenderer>();

var app = builder.Build();
app.MapDefaultControllerRoute();
app.Run();

Registering ChromePdfRenderer as a singleton is important. The renderer starts an internal Chromium subprocess the first time it is used. If you create a new instance per request you pay that startup cost on every call, which adds hundreds of milliseconds of latency under load. A singleton instance is thread-safe and handles concurrent render requests without additional configuration.

For a broader look at installation options including NuGet.config setups for private feeds, visit the installation overview.

How Do You Generate an Invoice PDF from an HTML String?

The most common on-the-fly use case is generating transactional documents -- invoices, receipts, order confirmations -- where the content changes per request but the layout stays constant.

The pattern is: build an HTML string with interpolated data, pass it to RenderHtmlAsPdf, and return the binary result as a file download.

using IronPdf;
using Microsoft.AspNetCore.Mvc;

public class DocumentController : Controller
{
    private readonly ChromePdfRenderer _renderer;

    public DocumentController(ChromePdfRenderer renderer)
    {
        _renderer = renderer;
    }

    [HttpGet("invoice/{orderId:int}")]
    public IActionResult GetInvoice(int orderId)
    {
        // In a real application, fetch this from your database or order service.
        var order = GetOrderData(orderId);

        string html = $"""
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="utf-8">
                <style>
                    body  {{ font-family: Arial, sans-serif; margin: 40px; color: #333; }}
                    h1   {{ color: #1a56db; }}
                    table {{ width: 100%; border-collapse: collapse; margin-top: 24px; }}
                    th, td {{ padding: 10px 14px; border: 1px solid #d1d5db; text-align: left; }}
                    th   {{ background: #f3f4f6; }}
                    tfoot td {{ font-weight: bold; }}
                </style>
            </head>
            <body>
                <h1>Invoice #{order.InvoiceNumber}</h1>
                <p>Date: {DateTime.UtcNow:yyyy-MM-dd} &nbsp;|&nbsp; Customer: {order.CustomerName}</p>
                <table>
                    <thead><tr><th>Item</th><th>Qty</th><th>Unit Price</th><th>Subtotal</th></tr></thead>
                    <tbody>
                        {string.Join("", order.Items.Select(i =>
                            $"<tr><td>{i.Name}</td><td>{i.Quantity}</td>" +
                            $"<td>${i.UnitPrice:F2}</td><td>${i.Quantity * i.UnitPrice:F2}</td></tr>"))}
                    </tbody>
                    <tfoot>
                        <tr><td colspan="3">Total</td><td>${order.Items.Sum(i => i.Quantity * i.UnitPrice):F2}</td></tr>
                    </tfoot>
                </table>
            </body>
            </html>
            """;

        var pdf = _renderer.RenderHtmlAsPdf(html);
        return File(pdf.BinaryData, "application/pdf", $"invoice-{orderId}.pdf");
    }
}

RenderHtmlAsPdf processes the full HTML document -- CSS grid, Flexbox, web fonts, even inline SVG -- using the same Chromium engine that powers Google Chrome. The returned PdfDocument exposes BinaryData (a byte[]) and Stream (a MemoryStream). Passing BinaryData to File() with "application/pdf" and a filename triggers a browser download.

For layouts that need pixel-perfect fidelity, consult the HTML to PDF rendering guide which covers responsive CSS, custom fonts, and JavaScript rendering.

What Does the Generated Invoice PDF Look Like?

Professional PDF invoice showing Invoice #123 dated 2025-11-13 with a styled header section, organized table containing Product A ($10.99) and Product B ($5.49), demonstrating dynamic PDF generation capabilities with custom CSS styling

How Do You Stream a PDF Directly to the Browser Without a Download Dialog?

Serving a PDF inline -- opening it in the browser's built-in viewer rather than downloading it -- requires two small changes: set Content-Disposition to inline and omit the filename from the File() call.

[HttpPost("report/preview")]
public async Task<IActionResult> PreviewReport([FromBody] ReportRequest request)
{
    string html = BuildReportHtml(request);

    var pdfDocument = await _renderer.RenderHtmlAsPdfAsync(html);

    // "inline" tells the browser to display rather than download.
    Response.Headers["Content-Disposition"] = "inline; filename=report.pdf";

    return new FileContentResult(pdfDocument.BinaryData, "application/pdf");
}

Using the async overload RenderHtmlAsPdfAsync is recommended for ASP.NET Core controllers because it frees the thread-pool thread while Chromium renders, keeping the server responsive under concurrent load.

How Does Memory-Based PDF Generation Work?

Generated PDF report showing 'Monthly Performance Review' title with formatted content demonstrating real-world business document generation including charts, metrics, and detailed analysis sections rendered from HTML template

The pdfDocument.BinaryData byte array lives entirely in managed memory. No intermediate file path is involved. The Content-Disposition header controls whether the PDF is displayed inline or offered as a download -- a browser behavior defined by the HTTP specification. For a deeper look at the MemoryStream approach, including streaming to Azure Blob Storage, visit the PDF memory stream documentation.

How Do You Generate PDFs from Entity Framework Core Query Results?

Most business applications pull report data from a database rather than constructing it at call time. The pattern below queries Entity Framework Core, builds an HTML table, and returns a PDF -- all within a single controller action.

[HttpGet("report/monthly")]
public async Task<IActionResult> MonthlyReport(int year, int month)
{
    // Pull aggregated transaction data from EF Core.
    var rows = await _dbContext.Transactions
        .Where(t => t.Date.Year == year && t.Date.Month == month)
        .GroupBy(t => t.Category)
        .Select(g => new { Category = g.Key, Count = g.Count(), Total = g.Sum(t => t.Amount) })
        .OrderByDescending(g => g.Total)
        .ToListAsync();

    string tableRows = string.Join("", rows.Select(r =>
        $"<tr><td>{r.Category}</td><td>{r.Count}</td><td>${r.Total:F2}</td></tr>"));

    string html = $"""
        <html><body style="font-family:Arial,sans-serif;padding:32px">
        <h1>Monthly Report -- {month:D2}/{year}</h1>
        <table style="width:100%;border-collapse:collapse">
          <thead>
            <tr style="background:#e5e7eb">
              <th style="padding:8px;border:1px solid #d1d5db">Category</th>
              <th style="padding:8px;border:1px solid #d1d5db">Transactions</th>
              <th style="padding:8px;border:1px solid #d1d5db">Total</th>
            </tr>
          </thead>
          <tbody>{tableRows}</tbody>
        </table>
        </body></html>
        """;

    var pdf = _renderer.RenderHtmlAsPdf(html);
    pdf.MetaData.Title  = $"Monthly Report {month:D2}/{year}";
    pdf.MetaData.Author = "Reporting System";

    return File(pdf.BinaryData, "application/pdf", $"report-{year}-{month:D2}.pdf");
}

Setting pdf.MetaData.Title and pdf.MetaData.Author embeds that information into the PDF's document properties, which is useful for compliance tracking and document management systems. For more sophisticated report layouts, consider CSS print styles, explicit page breaks, and embedded chart images.

How Do You Apply Headers, Footers, and Security to Generated PDFs?

Production documents often require running headers with the document title, page-number footers, and access controls that prevent unauthorized printing or copying. IronPDF's ChromePdfRenderOptions covers all of these requirements.

[HttpPost("document/secured")]
public async Task<IActionResult> GenerateSecuredDocument([FromBody] SecuredDocRequest request)
{
    var renderOptions = new ChromePdfRenderOptions
    {
        PaperSize       = PdfPaperSize.A4,
        MarginTop       = 45,
        MarginBottom    = 45,
        MarginLeft      = 25,
        MarginRight     = 25,
        EnableJavaScript = true,
        WaitFor         = new WaitFor { RenderDelay = 500 }
    };

    renderOptions.TextHeader = new TextHeaderFooter
    {
        CenterText       = request.DocumentTitle,
        DrawDividerLine  = true,
        FontSize         = 11
    };

    renderOptions.TextFooter = new TextHeaderFooter
    {
        LeftText  = "{date} {time}",
        RightText = "Page {page} of {total-pages}",
        FontSize  = 9
    };

    _renderer.RenderingOptions = renderOptions;

    var pdf = await _renderer.RenderHtmlAsPdfAsync(request.HtmlContent);

    if (request.RequirePassword)
    {
        pdf.SecuritySettings.OwnerPassword         = request.OwnerPassword;
        pdf.SecuritySettings.UserPassword          = request.UserPassword;
        pdf.SecuritySettings.AllowUserPrinting     = PdfPrintSecurity.NoPrint;
        pdf.SecuritySettings.AllowUserCopyPasteContent = false;
    }

    return File(pdf.BinaryData, "application/pdf", $"{request.FileName}.pdf");
}

The WaitFor.RenderDelay setting is particularly useful when your HTML includes charting libraries such as Chart.js or ApexCharts that finish drawing asynchronously. Setting a delay of 300-500 ms ensures Chromium captures the final rendered state. For documents that need to meet archival standards, combine the approach above with PDF/A compliance and digital signatures.

The {page} and {total-pages} tokens in footer text are resolved automatically by IronPDF at render time. Additional header and footer options include HTML-based headers for logo placement and per-section override capabilities.

What Rendering Options Are Available?

The table below summarizes the most useful ChromePdfRenderOptions properties for on-the-fly generation:

Commonly Used ChromePdfRenderOptions Properties
PropertyTypePurpose
PaperSizePdfPaperSizeSets page dimensions (A4, Letter, Legal, custom)
MarginTop / MarginBottomint (mm)Controls printable area spacing
EnableJavaScriptboolAllows JS execution before capture
WaitFor.RenderDelayint (ms)Delays capture for async rendering
TextHeader / TextFooterTextHeaderFooterRunning page headers and footers
HtmlHeader / HtmlFooterHtmlHeaderFooterHTML-formatted headers/footers with images
GrayScaleboolOutputs monochrome PDF
FitToPaperWidthboolScales wide content to fit page

What Are the Performance Best Practices for High-Volume PDF Generation?

When a single server handles hundreds of concurrent PDF requests, a few architectural decisions have an outsized impact on throughput and latency.

Singleton renderer registration. As shown in the installation section, registering ChromePdfRenderer as a singleton in the DI container avoids the cost of starting a new Chromium subprocess per request. According to Microsoft's ASP.NET Core performance guidance, minimizing object allocations and reusing expensive resources are the two most impactful optimizations available.

Always use async. RenderHtmlAsPdfAsync returns a Task<PdfDocument> and suspends the controller thread while Chromium works. This frees the thread pool to handle other incoming requests in parallel, which is why the async documentation recommends this overload for web hosts. The synchronous overload is appropriate only for console tools or background services where thread blocking is acceptable.

Stream directly, skip the intermediate array when possible. For large PDFs, .Stream can be written directly to the response body without materializing the full byte array:

[HttpGet("document/large")]
public IActionResult StreamLargeDocument(int documentId)
{
    string html = BuildLargeDocumentHtml(documentId);
    var pdf = _renderer.RenderHtmlAsPdf(html);

    // Stream.Position is already at 0; no seek needed.
    return File(pdf.Stream, "application/pdf", $"document-{documentId}.pdf");
}

Dispose after use. PdfDocument implements IDisposable. Wrapping it in a using statement releases the underlying memory buffer promptly, which matters when generating many large PDFs back to back:

using var pdf = _renderer.RenderHtmlAsPdf(html);
byte[] data = pdf.BinaryData;
// pdf is disposed here; data is safely copied to the local array.
return File(data, "application/pdf", "output.pdf");

For cloud deployment guidance covering Azure, AWS, Docker, and Linux environments, the IronPDF documentation provides environment-specific configuration notes. If the first render after startup is slow, consult the warm-up and caching guide for strategies to pre-initialize the renderer before the first user request arrives.

How Do You Add a Watermark to a Generated PDF?

A text or image watermark can be added to every page of the generated document before streaming:

[HttpGet("document/draft/{id:int}")]
public IActionResult GetDraftDocument(int id)
{
    string html = BuildDocumentHtml(id);
    var pdf = _renderer.RenderHtmlAsPdf(html);

    // Stamp "DRAFT" diagonally across every page.
    pdf.ApplyWatermark(
        "<h1 style='color:rgba(200,0,0,0.25);transform:rotate(-45deg)'>DRAFT</h1>",
        rotation: 45,
        opacity: 30
    );

    return File(pdf.BinaryData, "application/pdf", $"draft-{id}.pdf");
}

For full watermark configuration options including image watermarks and per-page control, see the watermark documentation.

NuGetInstall with NuGet

PM > Install-Package IronPdf

Install IronPDF by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

What Are Your Next Steps?

Dynamic PDF generation in ASP.NET Core follows a consistent pattern: build your HTML, call RenderHtmlAsPdf or its async overload, and return the result via a FileResult. IronPDF handles everything in between -- Chromium rendering, CSS application, JavaScript execution -- without requiring disk I/O at any stage.

From here, you can explore several directions depending on your application requirements. If your PDFs need to combine multiple source documents, the merge and split guide covers joining existing PDFs with freshly rendered pages. If you need users to fill and submit forms embedded in a PDF, the interactive forms documentation shows how to create and read field values. For regulated industries, PDF/A compliance and PDF/UA accessibility ensure documents meet archival and accessibility standards.

If you are evaluating IronPDF against alternatives, the iText vs IronPDF comparison provides a side-by-side technical breakdown. When you are ready to move to production, purchase a license to unlock all features and gain access to priority engineering support. The full API reference documents every class and method discussed in this guide.

For questions or issues during implementation, the engineering support team is available to help. For platform-specific notes on Blazor Server or MAUI, dedicated guides cover the configuration differences for each host model.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...
Read More

Related Articles

Key in blue circle

Get your free 30-day Trial Key instantly.

Your trial license will be sent to your email address

No limitations. 100% unlocked. No credit card.

OR
bullet_checkedNo credit card or account creation requiredNo limitations. 100% unlocked. No credit card.
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
Book your free Live Demo
Booking Badge

Trusted by Millions of Engineers Worldwide

Iron Software's customer logos
Get Your No-Obligation Consult
Complete the form below or email sales@ironsoftware.com
Your details will always be kept confidential.
Trusted by Millions of Engineers Worldwide
Iron Software's customer logos
Get your free 30-day Trial Key instantly.
No credit card or account creation required
C# NuGet Library for PDF
Install with NuGet

Version: 2026.9

PM > Install-Package IronPdf
nuget.org/packages/IronPdf/
  1. In Solution Explorer, right-click References, Manage NuGet Packages
  2. Select Browse and search "IronPdf"
  3. Select the package and install
C# PDF DLL
Download DLL

Version: 2026.9

or download Windows Installer here.

  1. Download and unzip IronPDF to a location such as ~/Libs within your Solution directory
  2. In Visual Studio Solution Explorer, right click References. Select Browse, "IronPdf.dll"

Licenses from $999