PDF stands for "Portable Document Format". It is, indeed, the standard for exchanging formatted documents on the internet. PDF documents are read by Adobe Acrobat Reader, most browsers, and even some popular word processors like Microsoft Word. It is a common used-case to generate PDF reports from live data. In this tutorial, I shall show you how you can easily generate a PDF report in an ASP.NET Core Razor Pages app and display data that originates from a Products API. We shall use the iText 7 library to accomplish this task.
Source Code: https://github.com/medhatelmasry/RazorPdfDemo
Companion Video: https://youtu.be/5KxxRbApoRY
The environment I am using is:
- Windows 11
- .NET version 6.0.100
- Visual Studio Code
The products API
Project setup
dotnet new razor -f net6.0 -o RazorPdfDemo
Change directory into the new folder and open the project inside VS Code with the following commands:
cd RazorPdfDemo
code .
Product model class
Id (int)SupplierId (int)CategoryId (int)QuantityPerUnit (string)UnitPrice (decimal)UnitsInStock (short)ReorderLevel (short)Discontinued (bool)Name (string)
We need to create a Product model class that represents the above JSON object. Therefore create a Models folder and add to it a Product class with the following content:
public partial class Product {[JsonPropertyName("id")]public int Id { get; set; }
[JsonPropertyName("supplierId")]public int? SupplierId { get; set; }[JsonPropertyName("categoryId")]public int? CategoryId { get; set; }[JsonPropertyName("quantityPerUnit")]public string? QuantityPerUnit { get; set; }[JsonPropertyName("unitPrice")]public decimal? UnitPrice { get; set; }[JsonPropertyName("unitsInStock")]public short? UnitsInStock { get; set; }[JsonPropertyName("unitsOnOrder")]public short? UnitsOnOrder { get; set; }[JsonPropertyName("reorderLevel")]public short? ReorderLevel { get; set; }[JsonPropertyName("discontinued")]public bool Discontinued { get; set; }[JsonPropertyName("name")]public string Name { get; set; } = null!;}
Generating PDF report
using iText.Kernel.Colors;using iText.Kernel.Geom;using iText.Kernel.Pdf;using iText.Kernel.Pdf.Canvas.Draw;using iText.Layout;using iText.Layout.Element;using iText.Layout.Properties;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.RazorPages;using RazorPdfDemo.Models;using System.Text.Json;namespace RazorPdfDemo.Pages;public class ReportModel : PageModel {private readonly ILogger<IndexModel> _logger;public ReportModel(ILogger<IndexModel> logger) {_logger = logger;}public async Task<IActionResult> OnGet() {MemoryStream ms = new MemoryStream();PdfWriter writer = new PdfWriter(ms);PdfDocument pdfDoc = new PdfDocument(writer);Document document = new Document(pdfDoc, PageSize.A4, false);writer.SetCloseStream(false);Paragraph header = new Paragraph("Northwind Products").SetTextAlignment(TextAlignment.CENTER).SetFontSize(20);document.Add(header);Paragraph subheader = new Paragraph(DateTime.Now.ToShortDateString()).SetTextAlignment(TextAlignment.CENTER).SetFontSize(15);document.Add(subheader);// empty linedocument.Add(new Paragraph(""));// Line separatorLineSeparator ls = new LineSeparator(new SolidLine());document.Add(ls);// empty linedocument.Add(new Paragraph(""));// Add table containing datadocument.Add(await GetPdfTable());// Page Numbersint n = pdfDoc.GetNumberOfPages();for (int i = 1; i <= n; i++) {document.ShowTextAligned(new Paragraph(String.Format("Page " + i + " of " + n)),559, 806, i, TextAlignment.RIGHT,VerticalAlignment.TOP, 0);}document.Close();byte[] byteInfo = ms.ToArray();ms.Write(byteInfo, 0, byteInfo.Length);ms.Position = 0;FileStreamResult fileStreamResult = new FileStreamResult(ms, "application/pdf");//Uncomment this to return the file as a download//fileStreamResult.FileDownloadName = "NorthwindProducts.pdf";return fileStreamResult;}private async Task<Table> GetPdfTable() {// TableTable table = new Table(4, false);// HeadingsCell cellProductId = new Cell(1, 1).SetBackgroundColor(ColorConstants.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph("Product ID"));Cell cellProductName = new Cell(1, 1).SetBackgroundColor(ColorConstants.LIGHT_GRAY).SetTextAlignment(TextAlignment.LEFT).Add(new Paragraph("Product Name"));Cell cellQuantity = new Cell(1, 1).SetBackgroundColor(ColorConstants.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph("Quantity"));Cell cellUnitPrice = new Cell(1, 1).SetBackgroundColor(ColorConstants.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph("Unit Price"));table.AddCell(cellProductId);table.AddCell(cellProductName);table.AddCell(cellQuantity);table.AddCell(cellUnitPrice);Product[] products = await GetProductsAsync();foreach (var item in products) {Cell cId = new Cell(1, 1).SetTextAlignment(TextAlignment.CENTER).Add(new Paragraph(item.Id.ToString()));Cell cName = new Cell(1, 1).SetTextAlignment(TextAlignment.LEFT).Add(new Paragraph(item.Name));Cell cQty = new Cell(1, 1).SetTextAlignment(TextAlignment.RIGHT).Add(new Paragraph(item.UnitsInStock.ToString()));Cell cPrice = new Cell(1, 1).SetTextAlignment(TextAlignment.RIGHT).Add(new Paragraph(String.Format("{0:C2}", item.UnitPrice)));table.AddCell(cId);table.AddCell(cName);table.AddCell(cQty);table.AddCell(cPrice);}return table;}private async Task<Product[]> GetProductsAsync() {HttpClient client = new HttpClient();var stream = client.GetStreamAsync("https://northwind.vercel.app/api/products");var products = await JsonSerializer.DeserializeAsync<Product[]>(await stream);return products!;}}
- The GetProductsAsync() method makes an HTTP GET request to endpoint https://northwind.vercel.app/api/products and reads products data. It then de-serializes the data into an array of Product objects and subsequently returns the array.
- The GetPdfTable() method does the following:
- The heading of the table is created. There will be four columns with titles: Product ID, Product Name, Quantity and Unit Price
- An array of Product objects is obtained from a call to the GetProductsAsync() method
- We iterate through each item in the array and add rows of data to the table
- The OnGet() method does the following:
- The first five lines in the OnGet() method sets up all the objects that are needed to generate a PDF document.
- A header with title "Northwind Products" is placed at the top of the report - center aligned.
- A sub-header with the current date is placed under the heading - also center aligned.
- This is followed by an empty line, a solid-line, and another empty line.
- The table containing product data is then displayed.
- Paging is added to the top right-side of each page
- Finally, the report is streamed down to the browser.
@page@model ReportModel
Let us add a menu item to the navigation of our web app. Open Pages/Shared/_Layout.cshtml and add the following markup code to the bottom of the <ul> .... </ul> navigation block:
<a class="nav-link text-dark" asp-area="" asp-page="/Report">Products PDF</a>
</li>
At this stage, let's run our web app and verify that we are indeed able to read data from the Northwind products API and subsequently generate a PDF report. Run your application with:
dotnet watch run
Point your browser to https://localhost:7292
NOTE: you will need to adjust the port number to suit your environment.
This is what the home page looks like:
Click on "Products PDF". You should soon after see the PDF document being generated in your browser:Conclusion
Using the iText 7 library is pretty straight forward when it comes to generating PDF documents. You can learn more at https://kb.itextpdf.com/home/it7kb/ebooks/itext-7-jump-start-tutorial-for-net.
This article is intended to provide you with a good starting point for generating PDF reports from your ASP.NET Razor Pages web apps.