Flexible PDF Reporting in .NET Using Razor Views
Tags: aspnetcore, razor, pdf, reporting
Published: 2024-06-30 10:50:50

---
title: Flexible PDF Reporting in .NET Using Razor Views
published: true
date: 2024-06-29 00:00:00 UTC
tags: aspnetcore,razor,pdf,reporting
canonical_url: https://www.milanjovanovic.tech/blog/flexible-pdf-reporting-in-net-using-razor-views
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y843q6xmwg3tmtum47yp.png
---
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](https://learn.microsoft.com/en-us/aspnet/core/mvc/views)are an HTML template with embedded [Razor](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/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](https://learn.microsoft.com/en-us/aspnet/core/mvc/overview),[Razor Pages](https://learn.microsoft.com/en-us/aspnet/core/razor-pages), and [Blazor](https://learn.microsoft.com/en-us/aspnet/core/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](https://tailwindcss.com/) 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.

```csharp
@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`](https://github.com/soundaranbu/Razor.Templating.Core) library. It provides a simple API to render a `.cshtml` file into a `string`.

```csharp
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:

```csharp
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.

```csharp
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](https://ironpdf.com/). 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.

```csharp
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](https://github.com/hardkoded/puppeteer-sharp). It's a .NET port of the [Puppeteer](https://github.com/puppeteer/puppeteer) library, which allows you to run a headless Chrome browser.

Another (conditionally) free option to consider is [NReco.PdfGenerator](https://www.nuget.org/packages/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:

```csharp
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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q68wmavhc6e9m8mv1q6i.png)

You can grab the source code for this sample [here](https://github.com/m-jovanovic/razor-view-html-to-pdf). 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:

- [PDF Reporting in .NET Using IronPDF and Razor Views](https://youtu.be/XYdcdVWsWos)
- [Creating PDF Reports With QuestPDF](https://www.milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore)

That's all for this week. Stay awesome!

* * *

**P.S. Whenever you're ready, there are 3 ways I can help you:**

1. [**Modular Monolith Architecture (NEW):**](https://www.milanjovanovic.tech/modular-monolith-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting) 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.

2. [**Pragmatic Clean Architecture:**](https://www.milanjovanovic.tech/pragmatic-clean-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting) 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.

3. [**Patreon Community:**](https://www.patreon.com/milanjovanovic) 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.