Introduction: The Power of PDF Generation in ASP.NET
Imagine this: Your e-commerce business is booming, but manually creating invoices is eating up hours of your team’s time. What if you could automate professional, dynamic PDF invoices using an aspnet pdf solution — directly from your ASP.NET application?
Enter QuestPDF—a modern, open-source library designed to simplify PDF generation in .NET applications. Unlike traditional tools like iTextSharp or PDFSharp, QuestPDF offers a fluent, code-first approach, making it easier to design complex documents without wrestling with low-level APIs.
In this guide, we’ll walk through building an invoice application in ASP.NET Core using QuestPDF. By the end, you’ll know how to:
- Set up QuestPDF in an ASP.NET project
- Design a reusable invoice template
- Dynamically populate data (customer details, line items, totals)
- Generate and download PDFs on demand
Let’s dive in!
Why QuestPDF? A Better Alternative for ASP.NET PDF Generation
Before jumping into code, let’s compare QuestPDF with other popular ASP.NET PDF libraries:
Feature | QuestPDF | iTextSharp | PDFSharp | Puppeteer |
---|---|---|---|---|
Ease of Use | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
Performance | High | Moderate | Moderate | Low (HTML rendering) |
Open Source | ✅ Yes | ❌ No (Paid) | ✅ Yes | ✅ Yes |
Fluent API | ✅ Yes | ❌ No | ❌ No | ❌ No |
HTML/CSS Support | ❌ No | ✅ Yes | ❌ No | ✅ Yes |
Why Choose QuestPDF?
✔ Declarative API – Define layouts like React components
✔ High Performance – Optimized for large documents
✔ No External Dependencies – Pure .NET library
✔ Active Development – Frequent updates & community support
If you need fine-grained control without wrestling with HTML-to-PDF quirks, QuestPDF is a perfect fit.
Setting Up Your ASP.NET Core Project for QuestPDF
Let’s get started by setting up your project. Here’s what you need:
1. Install the QuestPDF NuGet package
Install-Package QuestPDF
Or via CLI:
dotnet add package QuestPDF
2. Add a service to handle invoice generation
Let’s start by creating an Interface called IReportService.cs
and a class called ReportService.cs
that implements the interface where you’ll define the structure of the invoice.
The content of the interface should look like this:
public interface IReportService
{
byte[] RenderReport(InvoiceModel model);
}
The content of the class should look like this:
public sealed class ReportService : IReportService
{
public byte[] RenderReport(InvoiceModel model)
{
var logoBytes = File.ReadAllBytes(model.Company.Logo);
var borderColor = Color.FromHex("#D7DAE0");
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(0.7f, Unit.Centimetre);
page.PageColor(Color.FromHex("#FDFCFB"));
page.DefaultTextStyle(style => style.FontFamily("Inter"));
});
});
return document.GeneratePdf();
}
}
Recreating the Invoice Design (Step-by-Step with Code)
We’ll now recreate the invoice layout shown above using QuestPDF.
🧱 Structure Overview:
- Header (Logo, Contact Info)
- Billing Information & Invoice Details
- Table of Items
- Summary (Subtotal, Tax, Total)
- Footer Note & Terms
Let’s go!
Building the ASP.Net Pdf Invoice Header
Let’s create a private method within our main class to create the page header. The method looks like this:
private void RenderHeader(IContainer container, InvoiceModel model, byte[] logoBytes)
{
container.Column(headerCol =>
{
headerCol.Item().Row(headerRow =>
{
headerRow.RelativeItem().PaddingLeft(10).Column(col =>
{
col.Item().Container().Height(70).Width(80).Image(logoBytes);
col.Item().Text(model.Company.CompanyName).Bold().FontSize(15).FontColor(Colors.Blue.Accent2);
col.Item().Text($"{model.Company.Website}\n{model.Company.Email}").FontSize(10);
});
headerRow.RelativeItem().PaddingRight(10).AlignRight().Column(col =>
{
col.Item().Height(70);
col.Item().Text($"{model.Company.AddressLine1}\n{model.Company.AddressLine2}\n{model.Company.TaxId}")
.FontSize(10);
});
});
headerCol.Item().Height(20);
});
}
Building the ASP.Net Pdf Invoice Content
To creat the content of our invoice we will have to create a method called RenderBillingDetails which will also calls some other methods that will create client details, item details, and invoice summary.
private void RenderContent(IContainer container, InvoiceModel model, Color borderColor)
{
decimal subTotal = 0m;
container.Background(Colors.White).Border(1).BorderColor(borderColor).Padding(15).Column(column =>
{
RenderBillingDetails(column, model);
column.Item().Height(10);
RenderMetaDetails(column);
column.Item().Height(20);
column.Item().Table(table =>
{
RenderTableHeader(table, borderColor);
table.ColumnsDefinition(col =>
{
col.RelativeColumn();
col.ConstantColumn(60);
col.ConstantColumn(75);
col.ConstantColumn(105);
});
foreach (var item in model.Items)
{
table.Cell().Container().Padding(8).Text(text =>
{
text.Span(item.Name).FontColor(Colors.Black).FontSize(9);
text.EmptyLine();
text.Span(item.Description).FontColor(Color.FromHex("#666C7E")).FontSize(9);
});
table.Cell().Container().Padding(8).Text($"{item.Quantity}").AlignCenter();
table.Cell().Container().Padding(8).Text($"{item.Rate:C}").FontSize(9).FontColor(Colors.Black).AlignRight().Bold();
table.Cell().Container().Padding(8).Text($"{item.Amount:C}").FontSize(9).FontColor(Colors.Black).AlignRight().Bold();
subTotal += item.Amount;
}
var taxAmount = subTotal * (TaxPercent / 100);
var grandTotal = subTotal + taxAmount;
RenderSummaryRows(table, borderColor, subTotal, taxAmount, grandTotal);
});
column.Item().ExtendVertical().AlignBottom().AlignLeft()
.Text("Thanks for the business").FontSize(9).FontColor(Colors.Black).Bold();
});
}
private void RenderBillingDetails(ColumnDescriptor column, InvoiceModel model)
{
column.Item().Row(row =>
{
row.RelativeItem().Text(text =>
{
text.Span("Billed to").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine(); text.EmptyLine().LineHeight(0.5f);
text.Span(model.Custormer.CompanyName).FontColor(Color.FromHex("#333333")).Bold().FontSize(11);
text.EmptyLine();
text.Span(model.Custormer.AddressLine1).FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine();
text.Span(model.Custormer.AddressLine2).FontColor(Color.FromHex("#5E6470")).FontSize(10);
});
row.RelativeItem().Text(text =>
{
text.Span("Invoice number").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine(); text.EmptyLine().LineHeight(0.5f);
text.Span("#AB2324-01").FontColor(Color.FromHex("#333333")).Bold().FontSize(11);
});
row.RelativeItem().AlignRight().Text(text =>
{
text.Span("Invoice of (USD)").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine();
text.Span("$4,500.00").FontColor(Colors.Blue.Accent2).Bold().FontSize(14);
});
});
}
private void RenderMetaDetails(ColumnDescriptor column)
{
column.Item().Row(row =>
{
row.RelativeItem().Text(text =>
{
text.Span("Subject").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine(); text.EmptyLine().LineHeight(0.5f);
text.Span("Design System").FontColor(Color.FromHex("#333333")).Bold().FontSize(11);
});
row.RelativeItem().Text(text =>
{
text.Span("Invoice date").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine(); text.EmptyLine().LineHeight(0.5f);
text.Span(DateTime.UtcNow.ToString("dd MMM, yyyy")).FontColor(Color.FromHex("#333333")).Bold().FontSize(11);
});
row.RelativeItem().AlignRight().Text(text =>
{
text.Span("Due date").FontColor(Color.FromHex("#5E6470")).FontSize(10);
text.EmptyLine();
text.Span(DateTime.UtcNow.AddDays(15).ToString("dd MMM, yyyy")).FontColor(Color.FromHex("#333333")).Bold().FontSize(11);
});
});
}
private void RenderTableHeader(TableDescriptor table, Color borderColor)
{
var headerTitles = new[] { "ITEM DETAILS", "QTY", "RATE", "AMOUNT" };
table.Header(header =>
{
foreach (var title in headerTitles)
{
header.Cell().Element(e =>
e.Container()
.BorderTop(0.5f).BorderBottom(0.5f).BorderColor(borderColor)
.Padding(8)
.Text(text =>
{
text.Span(title).FontSize(9).ExtraBold().FontColor(Color.FromHex("#666C7E"));
if (title == "ITEM DETAILS")
text.AlignLeft();
else if (title == "QTY")
text.AlignCenter();
else
text.AlignRight();
})
);
}
});
}
Building the ASP.Net Pdf Invoice Footer
The final method will be the one that renders the footer.
private void RenderFooter(IContainer container)
{
container.Column(col =>
{
col.Item().Height(20);
col.Item().Text("Terms & Condition").FontColor(Color.FromHex("#666C7E")).FontSize(9).Bold();
col.Item().Height(10);
col.Item().Text("Please pay within 15 days of receiving this invoice").FontColor(Color.FromHex("#555555")).FontSize(10);
});
}
Updating the main method to call all the private methods
private const decimal TaxPercent = 10m;
public byte[] RenderReport(InvoiceModel model)
{
var logoBytes = File.ReadAllBytes(model.Company.Logo);
var borderColor = Color.FromHex("#D7DAE0");
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(0.7f, Unit.Centimetre);
page.PageColor(Color.FromHex("#FDFCFB"));
page.DefaultTextStyle(style => style.FontFamily("Inter"));
page.Header().Element(header => RenderHeader(header, model, logoBytes));
page.Content().Element(content => RenderContent(content, model, borderColor));
page.Footer().Element(RenderFooter);
});
});
return document.GeneratePdf();
}
ASP.Net PDF Rendering in Controller
You can generate and return the PDF via a simple controller action:
public class InvoiceController(IWebHostEnvironment env, IReportService invoiceService) : Controller
{
[HttpGet("invoice-pdf")]
public IActionResult GenerateInvoice()
{
var model = new InvoiceModel
{
Company = new Company()
{
Logo = Path.Combine(env.WebRootPath, "images", "coy-logo.png"),
CompanyName = "SoftDevBytes",
Website = "www.softdevbytes.com",
Email = "mark@softdevbytes.com",
AddressLine1 = "Business Address",
AddressLine2 = "City, State",
TaxId = "Tax ID 00XXXXXXX"
},
Custormer = new Company()
{
CompanyName = "Panda, Inc",
Website = "www.panda.com",
Email = "john@panda.com",
AddressLine1 = "Business Address",
AddressLine2 = "City, State",
},
Items = new List<InvoiceItem>
{
new() { Name = "Item Name", Description = "Item description", Quantity = 1, Rate = 3000 },
new() { Name = "Item Name", Description = "Item description", Quantity = 1, Rate = 1500 },
}
};
var reportByte = invoiceService.RenderReport(model);
return File(reportByte, "application/pdf", "invoice.pdf");
}
}
The final output should look like so:

Key Takeaways
Feature | Value for Developers |
---|---|
Visual Accuracy | Matches Figma/Designs exactly |
Performance | Handles thousands of invoices fast |
Open Source | Free for commercial use |
Custom Layouting | No rigid templates, full layout control |
ASP.NET Compatibility | Works out-of-the-box with MVC or Minimal API |
Conclusion: QuestPDF is Your Best Bet for ASP.Net PDF
Generating a PDF invoice shouldn’t feel like reinventing the wheel every time. With QuestPDF, you can maintain total control over design, performance, and logic within your .NET app.
Whether you’re a freelancer or an enterprise dev, this library helps you meet professional PDF demands with elegance.
💬 What’s Next?
- ⭐ Try integrating QuestPDF in your project and share the results!
- 📥 Download the source code here.
- 📚 Explore more on the official QuestPDF docs.
- 🔔 Subscribe for more ASP.NET Core guides.