Revolutionizing PDF Generation in C# with QuestPdf
Written on
Chapter 1: Introduction to QuestPdf
Are you fed up with the hassle of converting HTML to PDF? Frustrated with trying to ensure page numbers adjust dynamically regardless of table length? Is your report layout looking chaotic?
Well, your solution is here—QuestPdf! And the best part? It's open-source.
First, let's give a shoutout to Marcin Ziąbek, the genius behind QuestPdf. This remarkable tool has saved us countless hours and dollars in report generation. We've found it to be at least 50% faster than AsposePdf!
To get started, simply copy the code below and customize it to your needs. Think of it as receiving a fully assembled bike—you just need to remove the unnecessary accessories.
Section 1.1: Getting Started
QuestPdf comes with excellent documentation, but I’ll provide a practical example and the underlying code to illustrate its capabilities. This example closely resembles the statements we currently send to customers—of course, with some modifications.
To kick things off, install the library using NuGet for both QuestPdf and QuestPdf.Previewer (if you're using .NET 6).
Now, let's outline what we’re going to create:
- A page featuring a table, shapes, and images.
- A table that includes a chart!
As you can see, there’s a lot going on here, which is the goal. I want to showcase not only its power but also its efficiency compared to the overpriced alternatives.
Step 1: Implementing a Unit Test or Using the Hot Reload Previewer
Before diving deeper, let’s highlight the Hot Reload feature, available only in .NET 6. This allows you to see changes in real-time, a significant advantage. Here’s what you need:
using System.Diagnostics;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Fluent;
using QuestPDF.Previewer;
using TechShowcase.Demo;
// Quest PDF Demo
var model = new InvoiceModel
{
FullName = "Bob Smith"
};
var document = new Statement(model);
try
{
document.GeneratePdf("QuestPdfDemoTest");
}
catch (DocumentLayoutException e)
{
Debug.WriteLine(e.ElementTrace);
}
document.ShowInPreviewer();
When using Visual Studio, hit "Hot Reload." If you're on JetBrains Rider, simply run dotnet watch in the terminal for a live editing experience.
Keep in mind the DocumentLayoutException—these exceptions can help you identify where issues occur during debugging.
If you haven't upgraded to .NET 6 yet, create a Test Harness to view your results after each change. Below is an example using the xUnit testing library to execute a single test on the PDF generator and open the file.
public class InvoiceModel
{
public string FullName { get; set; }
}
public class QuestTest
{
[Fact]
public void QuestPdfTest()
{
var filePath = "statement.pdf";
var model = new InvoiceModel
{
FullName = "Bob Smith"};
var document = new Statement(model);
document.GeneratePdf(filePath);
Process.Start("explorer.exe", filePath);}
}
Section 1.2: Creating the Document
Now that you’ve set up the model and generated your PDF, it's time to create the Statement class, inheriting from IDocument. The Compose method is essential for the interface.
Here’s how your document should be structured for context:
public class Statement : IDocument
{
public InvoiceModel Model { get; }
public Statement(InvoiceModel model)
{
Model = model;}
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
FontManager.RegisterFont(File.OpenRead($"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}/fonts/ModernEraRegular.ttf"));
container.Page(page =>
{
page.Margin(20);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().Element(ComposeFooter);
});
}
}
Chapter 2: Building the Document Structure
Let's delve into creating the header, body, and footer of your document.
The Header
Begin by establishing a Constant class to manage all image paths, color variables, and font names. For clarity, I’ve left variables in place for now.
Here’s an example of how to structure the header:
void ComposeHeader(IContainer container)
{
var logo = $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}/img/NASA.jpg";
var regularFont = "Modern Era";
container.Row(row =>
{
row.ConstantItem(200).Column(column =>
{
column.Item().Width(50).Height(50).Image(logo);
column.Spacing(23.3f);
column.Item().LineHorizontal(1).LineColor("#B2CFD6");
});
row.RelativeItem().Column(column =>
{
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.FontFamily(regularFont).FontColor("#006179").FontSize(10));
text.AlignRight();
text.Line("All Aussie Adventures in Dotnet,");
text.Line("Wollongong London, SE1");
text.Line("Tel: 03819 1232");
text.Span("Email: ");
text.Span("[email protected]").FontColor("#FF40E0D0").FontFamily(regularFont);
text.EmptyLine();
});
column.Item().LineHorizontal(1).LineColor("#B2CFD6");
});});
}
The Body
To make your work modular, treat each section as a "Component," similar to how Blazor operates. Below is an example of how to structure the first page content.
void ComposeContent(IContainer container)
{
container.PaddingVertical(20).DefaultTextStyle(x => x.FontFamily("Modern Era").FontColor("#006179")).Column(column =>
{
column.Item().Text("What the benefits from your All Aussie Adventures Pension might be worth on DD Month YYYY").Bold().FontSize(20).FontColor("#006179");
column.Item().Text("Below is an example of a yearly investment growth rate:");
column.Item().Element(ComposeTable);
column.Item().Text("* A negative growth rate after inflation will reduce the buying power of your All Aussie Adventures Personal Pension over time.").FontSize(8);
column.Item();
ComposePageTwoRectangle(column);
column.Item().PageBreak();
});
}
For the complete code and more detailed steps, please refer to the original documentation or the provided resources.
Conclusion
In summary, you've now set up a functional PDF generator that efficiently creates well-structured documents, saving you considerable time. Once again, hats off to the creator of this incredible library.
If you have any questions, feel free to reach out. Thanks for joining us on this exciting journey through .NET development! 🐨