# Flexible PDF Reporting in .NET Using Razor Views

> In this article, we'll explore the power of using Razor views for flexible PDF reporting in .NET. We'll see how to create report templates with Razor views, convert them to HTML, and then transform that HTML into beautifully formatted PDF documents.

Published: 2024-06-29. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/flexible-pdf-reporting-in-net-using-razor-views

To generate PDF reports in .NET, build the report template as a Razor view, render it to an HTML string, and convert that HTML into a PDF with an HTML-to-PDF library.
You get full control over formatting and can style the document with modern CSS.

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 {4}
@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`.

```powershell
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**](https://milanjovanovic.tech/blog/minimal-apis-dotnet) 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:

![Invoice report.](https://milanjovanovic.tech/blogs/mnw_096/invoice.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://milanjovanovic.tech/blog/how-to-easily-create-pdf-documents-in-aspnetcore)

That's all for this week. Stay awesome!

---

## Frequently asked questions

### How do you generate PDF reports in .NET using Razor views?

Build the report template as a Razor view, render it to an HTML string with the Razor.Templating.Core library, then convert that HTML into a PDF with an HTML-to-PDF library. This gives you full control over formatting and lets you style the document with modern CSS.

### Can you use Razor views outside of ASP.NET Core MVC?

Yes. Razor views (.cshtml files) are commonly used in ASP.NET Core MVC, Razor Pages, and Blazor, but you can also define them in a class library or an ASP.NET Core Web API project and render them programmatically.

### How do you render a Razor view to an HTML string in .NET?

Use the Razor.Templating.Core library. Call RazorTemplateEngine.RenderAsync with the path to the .cshtml file and the model instance. If you want dependency injection support, register IRazorTemplateEngine by calling AddRazorTemplating after registering your other services.

### What libraries can convert HTML to PDF in .NET?

IronPDF is a paid option whose ChromePdfRenderer uses an embedded Chrome browser. Free alternatives include Puppeteer Sharp, a .NET port of Puppeteer that runs headless Chrome, and NReco.PdfGenerator, which is free only for single-server deployments.

### Can you style a PDF generated from HTML with CSS?

Yes. The CSS you write in the Razor view, either inline or from a referenced stylesheet, is applied when the HTML is exported to PDF. You can even use a utility framework like Tailwind CSS to style the report.
