asp-net-pdf-a-guide-to-building-an-invoice-app-with-quest-pdf

ASP.NET PDF Guide: Building an Invoice App with QuestPDF

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:

FeatureQuestPDFiTextSharpPDFSharpPuppeteer
Ease of Use⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
PerformanceHighModerateModerateLow (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

FeatureValue for Developers
Visual AccuracyMatches Figma/Designs exactly
PerformanceHandles thousands of invoices fast
Open SourceFree for commercial use
Custom LayoutingNo rigid templates, full layout control
ASP.NET CompatibilityWorks 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?