Flexible PDF Reporting in .NET Using Razor Views
I'll never forget when I was working on a project that required generating weekly sales reports for a client. The initial solution involved a clunky process of exporting data, manipulating it in spreadsheets, and manually creating PDFs. It was tedious, error-prone, and it sucked up way too much of my time. I knew there had to be a better way.
That's when I discovered the power of combining Razor views with HTML-to-PDF conversion. You have more control over formatting the document. You can use modern CSS to style the HTML markup, which will be applied when exporting to a PDF document. It's also simple to implement in ASP.NET Core.
Here's what we'll cover:
Understanding Razor views
Converting Razor views to HTML
HTML to PDF conversion in .NET
Putting it all together with Minimal APIs
Let's dive in!
Razor Views
Razor views are an HTML template with embedded Razor markup. Razor allows you to write and execute .NET code inside a web page. Views have a special .cshtml
file extension. They're commonly used in ASP.NET Core MVC, Razor Pages, and Blazor.
However, you can define Razor views in a class library or ASP.NET Core Web API project.
You can use the Model
object to pass in data to a Razor view. Inside the .cshtml
file, you'll specify the model type with the @model
keyword. In the example below, I'm specifying that the Invoice
class is the model for this view. You can access the model instance with the Model
property in the view.
This is the InvoiceReport.cshtml
view we'll use to generate a PDF invoice.
You can write CSS in the Razor view inline or reference a stylesheet. I'm using the Tailwind CSS utility framework, which uses inline CSS. I usually delegate this to a front-end engineer on my team so they can stylize the report as needed.
@using System.Globalization
@using HtmlToPdf.Contracts
@model HtmlToPdf.Contracts.Invoice
@{
IFormatProvider cultureInfo = CultureInfo.CreateSpecificCulture("en-US");
var subtotal = Model.LineItems.Sum(li => li.Price * li.Quantity).ToString("C", cultureInfo);
var total = Model.LineItems.Sum(li => li.Price * li.Quantity).ToString("C", cultureInfo);
}
<script src="https://cdn.tailwindcss.com"></script>
<div class="min-w-7xl flex flex-col bg-gray-200 space-y-4 p-10">
<h1 class="text-2xl font-semibold">Invoice #@Model.Number</h1>
<p>Issued date: @Model.IssuedDate.ToString("dd/MM/yyyy")</p>
<p>Due date: @Model.DueDate.ToString("dd/MM/yyyy")</p>
<div class="flex justify-between space-x-4">
<div class="bg-gray-100 rounded-lg flex flex-col space-y-1 p-4 w-1/2">
<p class="font-medium">Seller:</p>
<p>@Model.SellerAddress.CompanyName</p>
<p>@Model.SellerAddress.Street</p>
<p>@Model.SellerAddress.City</p>
<p>@Model.SellerAddress.State</p>
<p>@Model.SellerAddress.Email</p>
</div>
<div class="bg-gray-100 rounded-lg flex flex-col space-y-1 p-4 w-1/2">
<p class="font-medium">Bill to:</p>
<p>@Model.CustomerAddress.CompanyName</p>
<p>@Model.CustomerAddress.Street</p>
<p>@Model.CustomerAddress.City</p>
<p>@Model.CustomerAddress.State</p>
<p>@Model.CustomerAddress.Email</p>
</div>
</div>
<div class="flex flex-col bg-white rounded-lg p-4 space-y-2">
<h2 class="text-xl font-medium">Items:</h2>
<div class="">
<div class="flex space-x-4 font-medium">
<p class="w-10">#</p>
<p class="w-52">Name</p>
<p class="w-20">Price</p>
<p class="w-20">Quantity</p>
</div>
@foreach ((int index, LineItem item) in Model.LineItems.Select((li, i) => (i + 1, li)))
{
<div class="flex space-x-4">
<p class="w-10">@index</p>
<p class="w-52">@item.Name</p>
<p class="w-20">@item.Price.ToString("C", cultureInfo)</p>
<p class="w-20">@item.Quantity.ToString("N2")</p>
</div>
}
</div>
</div>
<div class="flex flex-col items-end bg-gray-50 space-y-2 p-4 rounded-lg">
<p>Subtotal: @subtotal</p>
<p>Total: <span class="font-semibold">@total</span></p>
</div>
</div>
Converting Razor Views to HTML
The next thing we'll need is a way to convert the Razor view into HTML. We can do this with the Razor.Templating.Core
library. It provides a simple API to render a .cshtml
file into a string
.
Install-Package Razor.Templating.Core
You can use the RazorTemplateEngine
static class to call the RenderAsync
method. It accepts the path to the Razor view and the model instance that will be passed to the view.
Here's what that will look like:
Invoice invoice = invoiceFactory.Create();
string html = await RazorTemplateEngine.RenderAsync(
"Views/InvoiceReport.cshtml",
invoice);
Alternatively, you can use the IRazorTemplateEngine
instead of the static class. In that case, you must call AddRazorTemplating
to register the required services with DI. This is also required if you want to use dependency injection inside the Razor views with @inject
. It's recommended that you call AddRazorTemplating
after registering all dependencies.
services.AddRazorTemplating();
HTML to PDF conversion
Now that we've converted our Razor view into HTML, we can use it to generate a PDF report. Many libraries offer this functionality. The library I've used most often is IronPDF. It's a paid library (and well worth it), but I know developers also want free options, so I'll list some alternatives at the end.
We can use IronPDF's ChromePdfRenderer
, which uses an embedded Chrome browser. The renderer exposes the RenderHtmlAsPdf
method, which generates a PdfDocument
. Once you have the document, you can store it on the file system or export it as binary data.
var renderer = new ChromePdfRenderer();
using var pdfDocument = renderer.RenderHtmlAsPdf(html);
pdfDocument.SaveAs($"invoice-{invoice.Number}.pdf");
If you're looking for free options, check out Puppeteer Sharp. It's a .NET port of the Puppeteer library, which allows you to run a headless Chrome browser.
Another (conditionally) free option to consider is NReco.PdfGenerator. However, it's only free for single-server deployments.
Putting It All Together
Let's use everything we discussed to create a Minimal API endpoint to generate an invoice PDF report and return it as a file response. Here's the code snippet:
app.MapGet("invoice-report", async (InvoiceFactory invoiceFactory) =>
{
Invoice invoice = invoiceFactory.Create();
var html = await RazorTemplateEngine.RenderAsync(
"Views/InvoiceReport.cshtml",
invoice);
var renderer = new ChromePdfRenderer();
using var pdfDocument = renderer.RenderHtmlAsPdf(html);
return Results.File(
pdfDocument.BinaryData,
"application/pdf",
$"invoice-{invoice.Number}.pdf");
});
This is what the generated PDF report looks like:
You can grab the source code for this sample here. Feel free to try out a different library for HTML to PDF conversion.
Summary
In this article, we've explored the power of using Razor views for flexible PDF reporting in .NET. We've seen how to create report templates with Razor views, convert them to HTML, and then transform that HTML into beautifully formatted PDF documents.
Whether you need to generate invoices, sales reports, or any other kind of structured document, this approach offers a simple and customizable solution.
Here's what you can explore next:
That's all for this week. Stay awesome!
P.S. Whenever you’re ready, there are 3 ways I can help you:
Modular Monolith Architecture (NEW): Join 650+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
Pragmatic Clean Architecture: Join 2,800+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
Patreon Community: Join a community of 1,050+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.
Subscribe to my newsletter
Read articles from Milan Jovanović directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Milan Jovanović
Milan Jovanović
I'm a seasoned software architect and Microsoft MVP for Developer Technologies. I talk about all things .NET and post new YouTube videos every week.