PDF Reports in .NET Core
I recently needed to produce a PDF report with a .NET Core app. My first thought was to turn to the Microsoft RDLC Report Designer, which I learned to use during my first programming job, and I know works well.
Alas, I was surprised to discover that that tool is not compatible with .NET Core, and in fact the platform doesn’t provide any built in reporting functionality. I voted for it to be included as a feature here, and I encourage you to do the same.
But in the meantime, I needed to find an alternative. After trying and failing with a couple purported solutions, like this package, I settled on DinkToPDF, which has a detailed step by step tutorial:
In this post I will describe the things I learned while following those steps which aren’t included in the above tutorial.
Attendant Files
In order to get DinkToPDF to work, you will need to make a libwkhtmltox.dll, .dylib and .so file available to your application. The tutorial implies that these files will be installed with the nuget package, but I did not find that to be the case.
You can find them in the source repository on github in the /v0.12.4/32 Bit/ and /64 Bit/ folders. Download or clone the repository and copy those files from the appropriate folder into your root application folder.
Also, I needed to change the “Copy to Output Directory” setting in Visual Studio while I was debugging with IIS Express, not just before publishing the app to the server.
Entity Framework Classes
DinkToPdf converts an HTML template string into a PDF. I wasn’t sure if it would integrate with my entity framework classes as easily as an RDLC would have.
It turned out to be simple to swap them in to the same loop in the tutorial that adds a row to an HTML table for each item in a C# list. I was also able to do all the advanced stuff that I was hoping the RDLC would help me do, like sorting and grouping records, with LINQ before looping through the list.
List<ProjectPlan> projectPlans =
await (from project in _context.Project
select new ProjectPlan
{
Projnum = project.Projnum,
Projname = project.Projname,
Comments = project.Comments,
Fiscalyear = project.Fiscalyear
}).ToListAsync();
projectPlans = projectPlans
.GroupBy(x => x.Projnum)
.Select(grp => new ProjectPlan
{
Projnum = grp.Key,
Projname = grp.First().Projname,
Comments = grp.First().Comments,
Fiscalyear = grp.First().Fiscalyear
})
.OrderBy(x => x.Projname)
.ToList();
foreach (ProjectPlan projectPlan in projectPlans)
{
sb.AppendFormat(@"<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
<td>{3}</td></tr>",
projectPlan.Projnum,
projectPlan.Projname,
projectPlan.Comments,
projectPlan.Fiscalyear);
}
CSS Styling
The HTML used to render the report is styled with CSS. My report is hundreds of pages long, so I wanted the table header to repeat on each page. This can be accomplished using these CSS tags.
table {
page-break-inside: auto;
}tr {
page-break-inside: avoid;
page-break-after: auto;
}thead {
display: table-header-group;
}
For some reason, some combination of the tags or the font I used removed the default bold style from the header rows. The only way I was able to get it back was using this:
thead {
font-weight: 900;
}
Error Messaging
My PDF file should open when the user clicks on a hyperlink. The tutorial explains how to do this by returning a named File object with the method that is called.
But if there is an error anywhere in the process, I want the browser to show an error message instead. There are many ways to do this. I decided a quick solution would be to change my hyperlink target to a new tab in order to make the results flexible.
<a asp-page="/Reports/Database_Reports"
asp-page-handler="PDFProjectName"
target="_blank">Sorted by Project Name</a>
This way, if a PDF is successfully created, it will be downloaded to the new tab, and if an error message is created, it will be displayed as text in the new tab. Like, for example, the error that arises if you don’t pick correctly between the 32 and 64 bit versions of libwkhtmltox.
catch (Exception e) {
return Content(e.Message);
}
A nice side effect of this, is you also get a wait spinner in the header of the new tab while the PDF is rendering, which does take a while for my hundreds of pages.
I still hope Microsoft will bring the RDLC to .NET Core, but this will hold me over nicely for now.