Generate a PDF from ASP.NET Core for free

No matter what system I have been working on, generating PDF files already snuck in somehow. Whether it is generating an invoice or a report of data, PDF files are still an essential part of most web applications. In this post, I'll show you how to generate a PDF file from an ASP.NET Core (MVC) view.

Back when we implemented our invoices on elmah.io, I was looking for a good PDF component for our invoicing service (ASP.NET MVC at the time but now upgraded to core). Pretty much all PDF generating packages are based on wkhtmltopdf. wkhtmltopdf is a command-line tool able to generate a PDF from HTML, available for most operating systems. Calling a CLI from a web app isn't exactly code you want to be displayed in your source code. Luckily, a lot of open source projects wrapping the usage of wkhtmltopdf exists out there. To be fair, we chose NReco.PdfGenerator (paid component) for our service. For now, I will show you how to create a similar PDF generating feature for free.

To start generating PDF files, create a new ASP.NET Core MVC 3.1 project. Next, install the Wkhtmltopdf.NetCore NuGet package:

Install-Package Wkhtmltopdf.NetCore

Wkhtmltopdf.NetCore is a free wrapper of wkhtmltopdf that also integrates nicely with ASP.NET Core. Call the AddWkhtmltopdf method in the ConfigureServices method in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddWkhtmltopdf("wkhtmltopdf");
}

The "wkhtmltopdf" parameter is the path to the wkhtmltopdf executable, that you will need to provide manually. You might wonder why Wkhtmltopdf.NetCore doesn't come with this dependency, but that's a good thing. New versions of wkhtmltopdf are released and there is no official NuGet package for it. This means that a bundled version of wkhtmltopdf would make it harder to keep up with new versions of wkhtmltopdf.

To include wkhtmltopdf in your project, you need to decide how you want to include it. People built NuGet packages (like wkhtmltopdf.x64). In my experience, using one of these packages can be a problem since they typically include the version of wkhtmltopdf that the creator needed and haven't been updated since that. So, you are left with either including the executable in your source code or building a NuGet package and hosting it on an internal NuGet feed (Azure DevOps and MyGet both offer NuGet servers).

For this post, I'll include the executable directly in the source code. Having binary files in Git isn't ideal, but I'm sure you will find a solution that fits your temper. Download the most recent version of wkhtmltopdf for Windows from https://wkhtmltopdf.org and place it in the directory wkhtmltopdf\Windows in your project folder. The Wkhtmltopdf.NetCore package expects a Windows folder inside the folder you specified in the setup. To make sure that the file is copied to the out directory, right-click it in Visual Studio and select Copy if newer:

Finally! We are ready to generate the PDF file. Inside HomeController inject an IGeneratePdf object in the constructor:

readonly IGeneratePdf generatePdf;

public HomeController(IGeneratePdf generatePdf)
{
    this.generatePdf = generatePdf;
}

The IGeneratePdf was automatically set up when we called the AddWkhtmltopdf method in the previous step.

Replace the content in the Views\Home\Index.cshtml file with the following:

@model string

<!DOCTYPE html>
<html>
    <body>
        <h1>@Model</h1>
    </body>
</html>

In your case you will need to design the page for the PDF, but this simple example show how to generate some HTML with a model as input for wkhtmltopdf.

Be aware that Wkhtmltopdf.NetCore doesn't seem to support Layouts and partial views. If you have a layout in the Shared folder, include the following in the top of your view since it is self-contained:

@{ Layout = null; }

The only thing missing is to update the Index action inside HomeController.cs:

public async Task<IActionResult> Index()
{
    return await _generatePdf.GetPdf("Views/Home/Index.cshtml", "Hello World");
}

Hit F5 and ... Magic!