Skip to main content
SEMastery
ASP.NETbeginner

How to Create and Convert PDF Documents in ASP.NET Core

Learn to create and convert PDF documents in ASP.NET Core using QuestPDF and HTML-to-PDF tools, with simple code, diagrams, and clear advice.

11 min readUpdated November 10, 2025

A receipt from the sweet shop

Think about your favourite sweet shop. You buy a box of laddoos, and the shopkeeper hands you a printed bill. That bill always looks the same no matter which printer or phone you use to view it. The amount, the date, the shop name — they never move around or change size.

A PDF (Portable Document Format) is exactly like that printed bill. It is a fixed paper. Once you make it, it looks the same on every computer, phone, and printer in the world. That is why offices use PDFs for invoices, report cards, tickets, and certificates.

In this guide, you will learn two things in ASP.NET Core:

  1. How to create a brand-new PDF from scratch using C# code.
  2. How to convert existing HTML or a web page into a PDF.

We will keep the code small and friendly. By the end, you will be able to send a real PDF back to a browser.

Two ways to make a PDF

There are two main roads to a PDF in .NET. Picture them like two ways to make that sweet-shop bill.

  • Road 1 — Build it yourself. You take a blank page and place each piece of text and each line by hand. This is the code-first way. A popular library for this is QuestPDF.
  • Road 2 — Print a web page. You already have an HTML page that looks nice. You ask a tool to "print" that page into a PDF. This is the HTML-to-PDF way. Tools like IronPDF, Syncfusion, and NReco do this.
The two roads to creating a PDF in ASP.NET Core

Both roads end in the same place: a stream of bytes that you send back to the user. Let us walk each road.

Road 1: Create a PDF with QuestPDF

QuestPDF lets you design a document using plain C#. You describe the page in a clear, readable way, almost like building blocks. It is fast and works well on servers.

Step 1: Install the package

Open a terminal in your project folder and run this command.

// In a terminal, not in C#:
// dotnet add package QuestPDF

If you prefer the NuGet UI in Visual Studio, search for QuestPDF and click install.

Step 2: Set the license once

QuestPDF asks you to declare which license you are using. This is one line at startup. For learning, small companies, open-source, or charity work, the free Community license is fine.

using QuestPDF.Infrastructure;
 
// In Program.cs, before the app runs:
QuestPDF.Settings.License = LicenseType.Community;

A quick word on the license, because it matters. QuestPDF is free under its Community MIT License if your company earns less than $1M USD in yearly gross revenue, or if you use it for learning, open-source, or charity. Companies above that limit need a paid Professional or Enterprise license. Always check the official license page before shipping a paid product.

Step 3: Describe the document

Now the fun part. We build a small invoice. Notice how the code reads almost like a sentence: a page, with some margin, a header, some content, and a footer.

using QuestPDF.Fluent;
using QuestPDF.Helpers;
 
public static byte[] BuildInvoicePdf(string customer, decimal total)
{
    return Document.Create(doc =>
    {
        doc.Page(page =>
        {
            page.Margin(40);
            page.Size(PageSizes.A4);
 
            page.Header()
                .Text("Sweet Shop Invoice")
                .FontSize(22).Bold();
 
            page.Content().PaddingVertical(20).Column(col =>
            {
                col.Item().Text($"Customer: {customer}");
                col.Item().Text($"Date: {DateTime.Now:dd MMM yyyy}");
                col.Item().PaddingTop(10)
                    .Text($"Total: ₹{total}").FontSize(16).Bold();
            });
 
            page.Footer()
                .AlignCenter()
                .Text("Thank you for shopping with us!");
        });
    }).GeneratePdf(); // returns a byte[]
}

The method returns a byte[] — the raw PDF. That byte array is all we need to send the file to the user.

Step 4: Send it from a controller

Here is a tiny controller action. It builds the PDF, then hands it back with the right content type so the browser knows it is a PDF.

using Microsoft.AspNetCore.Mvc;
 
[ApiController]
[Route("invoices")]
public class InvoiceController : ControllerBase
{
    [HttpGet("{customer}")]
    public IActionResult Get(string customer)
    {
        byte[] pdf = BuildInvoicePdf(customer, total: 250m);
        return File(pdf, "application/pdf", "invoice.pdf");
    }
}

When someone visits /invoices/Asha, ASP.NET Core sends back invoice.pdf. The browser shows it or downloads it. That is a complete, working PDF feature.

QuestPDF request flow

Request
Build doc
Generate bytes
Return file

Steps

1

Request

User hits /invoices/Asha

2

Build doc

Describe page in C#

3

Generate bytes

GeneratePdf() runs

4

Return file

File(bytes, application/pdf)

What happens when a user asks for a PDF invoice

How QuestPDF lays out the page

QuestPDF measures your content and decides where each part goes. If your content is too tall for one page, it moves the extra onto a new page automatically. You do not have to count pixels.

How QuestPDF turns C# blocks into pages

Road 2: Convert HTML to a PDF

Sometimes you already have a nice HTML page. Maybe it is a Razor view with your company colours and a logo. Rebuilding all of that in C# would be a waste. Instead, you can convert the HTML into a PDF.

The idea is simple: a tool takes your HTML and CSS, renders it like a browser would, and then saves that picture as a PDF.

HTML to PDF conversion pipeline

A simple HTML-to-PDF example

Many tools share a similar shape: give it HTML, get back bytes. Here is the pattern with a popular library, IronPDF. The exact class names differ between tools, but the steps are the same.

using IronPdf;
 
public static byte[] BuildReportFromHtml(string title, string body)
{
    var html = $@"
        <html>
          <head><style>
            body {{ font-family: sans-serif; }}
            h1 {{ color: #1a4f8b; }}
          </style></head>
          <body>
            <h1>{title}</h1>
            <p>{body}</p>
          </body>
        </html>";
 
    var renderer = new ChromePdfRenderer();
    var pdf = renderer.RenderHtmlAsPdf(html);
    return pdf.BinaryData; // byte[]
}

Then you return it from a controller the same way as before, using File(bytes, "application/pdf", "report.pdf"). The big win here is that your CSS — colours, fonts, spacing — carries over, so the PDF matches your web page.

A note on HTML-to-PDF tools

Most strong HTML-to-PDF libraries are paid for commercial use. They are powerful and save time, but check the price and license before you commit. Here is a quick comparison to help you choose.

ToolStyleCostGood for
QuestPDFCode-firstFree under limitPrecise invoices and reports
IronPDFHTML to PDFPaidReusing HTML and CSS pages
SyncfusionHTML to PDFFree community / paidRich documents, many features
NReco.PdfGeneratorHTML to PDFFree (wkhtmltopdf)Simple jobs, older engine

Creating vs converting: which one?

Both roads make a PDF, but they suit different jobs. Use this table as a quick guide.

QuestionCreate (QuestPDF)Convert (HTML to PDF)
Do you need exact control?Yes, full controlLess, depends on CSS
Do you already have HTML?Not usedReuses it directly
Speed and memory on serversVery goodHeavier, uses a browser engine
Cost for a small teamOften freeOften paid
Learning curveLearn the C# APIJust write HTML

A simple rule: if you are starting fresh and want clean, fast, server-friendly documents, pick QuestPDF. If you already have polished HTML pages and want them to look identical in PDF, pick an HTML-to-PDF tool.

Choosing your approach

Have HTML?
Yes path
No path
Decide

Steps

1

Have HTML?

Do you own a nice HTML page

2

Yes path

Use HTML to PDF tool

3

No path

Build with QuestPDF

4

Decide

Check cost and control

A short decision path for picking a PDF method

Good habits for PDFs on a server

A few simple tips will save you trouble later.

  • Do the work off the request thread for big files. Generating a 200-page PDF can take time. For heavy jobs, build the PDF in a background task or a queue, then let the user download it when it is ready.
  • Stream large files. Instead of holding the whole PDF in memory, you can write it straight to the response stream. This keeps memory low when many users ask at once.
  • Set the right headers. The content type must be application/pdf. To force a download, you can pass a file name; to show it inline, set the disposition to inline.
  • Watch fonts. A Linux server may not have the fonts your Windows machine has. QuestPDF bundles a safe default font, but if you use special fonts, ship the font files with your app.
  • Cache repeated PDFs. If the same report is requested many times and the data has not changed, store the bytes and reuse them.

Here is how streaming looks. We write the PDF straight into the HTTP response so we never keep the whole file in memory.

[HttpGet("stream/{customer}")]
public IActionResult Stream(string customer)
{
    Response.ContentType = "application/pdf";
 
    var document = Document.Create(doc =>
    {
        doc.Page(page =>
        {
            page.Margin(40);
            page.Content().Text($"Streaming invoice for {customer}");
        });
    });
 
    document.GeneratePdf(Response.Body); // writes directly to the stream
    return new EmptyResult();
}

Adding a table to your PDF

Most real documents have a table — a list of items with prices, or rows of marks on a report card. QuestPDF makes this easy. You define the columns once, add a header row, then loop over your data and add one row per item. The layout engine lines everything up for you and moves long tables onto new pages by itself.

Here is a small order table. Imagine the customer bought three kinds of sweets, and we want each on its own row with a price.

using QuestPDF.Fluent;
using QuestPDF.Helpers;
 
public static byte[] BuildOrderPdf((string Name, decimal Price)[] items)
{
    return Document.Create(doc =>
    {
        doc.Page(page =>
        {
            page.Margin(40);
            page.Size(PageSizes.A4);
            page.Header().Text("Order Summary").FontSize(20).Bold();
 
            page.Content().PaddingTop(15).Table(table =>
            {
                table.ColumnsDefinition(columns =>
                {
                    columns.RelativeColumn(3); // wide column for the name
                    columns.RelativeColumn(1); // narrow column for the price
                });
 
                table.Header(header =>
                {
                    header.Cell().Text("Item").Bold();
                    header.Cell().AlignRight().Text("Price").Bold();
                });
 
                foreach (var item in items)
                {
                    table.Cell().Text(item.Name);
                    table.Cell().AlignRight().Text($"{item.Price}");
                }
            });
        });
    }).GeneratePdf();
}

Read the code slowly and you can almost say it out loud: "two columns, a header row, then one row per item." That readability is the main reason people enjoy QuestPDF. When the table grows past one page, the header row even repeats on the next page so the reader never loses track of which column is which.

A common mistake to avoid

New developers often forget to set the QuestPDF license, then see an error at runtime. Set it once at startup in Program.cs. Do not set it inside every method — that is repeated work and easy to forget.

Another common slip is building huge PDFs on the web request itself and timing out. If a job is large, move it to the background. Your users get a smoother experience, and your server stays calm.

Quick recap

  • A PDF is like a printed bill: it looks the same everywhere, which is why it is perfect for invoices, reports, and certificates.
  • There are two roads: create a PDF from scratch in code, or convert existing HTML into a PDF.
  • QuestPDF is a clean, fast, code-first library. It is free under the Community MIT License for companies under $1M USD yearly revenue, and for learning, open-source, and charity use. Set the license once at startup.
  • To send a PDF, generate a byte[] and return File(bytes, "application/pdf", "name.pdf"). For big files, stream to Response.Body.
  • HTML-to-PDF tools (IronPDF, Syncfusion, NReco) reuse your HTML and CSS, but most are paid for commercial use, so check the license first.
  • For large documents, do the work in the background and mind your fonts on Linux servers.

References and further reading

Related Posts