IronPDFPhoto from Pexels

Originally Posted On: https://medium.com/%40rafay_khan/crystal-report-pdf-generate-in-asp-net-api-with-password-c1a40f9ffe9c

Building a Secure PDF Generator API with IronPDF

IronPDF is a powerful library used in many ASP.NET applications. Unlike Crystal Reports, which requires a specific report designer and runtime, IronPDF allows developers to design reports using standard HTML, CSS, and JavaScript. One of its strongest features is the ability to apply security settings, such as password protection, with just a few lines of code.

In this tutorial, we will demonstrate how to generate a password-protected PDF using IronPDF in an ASP.NET API.

Prerequisites

  1. Visual Studio (2019 or 2022)
  2. dotNET Framework 4.6.2+ (Compatible with the legacy ASP.NET Web API stack used in your example)
  3. Basic knowledge of C# and JSON

Step 1: Create the ASP.NET Web API Project

First, we need to set up the environment. If you already have an existing project, you can skip this step.

  • Open Visual Studio and click Create a new project.
  • Search for ASP.NET Web Application (.NET Framework).
  • Name your project (e.g., SecureReportApi) and click Create.
  • In the template selection screen, choose Web API.
  • Ensure “Configure for HTTPS” is checked, then click Create.

Step 1: Create a New .NET Project

Press enter or click to view image in full size

ASP.NET Web Application (.NET Framework)

You should select the Web API template when creating an API. However, you may choose a different template if it better suits your specific requirements.

Press enter or click to view image in full size

Select Web API (enable Configure for HTTPS) to create a secure RESTful service

I save .rpt in my project folder with name “Reports” and i also add config.json file on same location where my rpt is present.

Press enter or click to view image in full size

Recommended configuration for a new .NET 8 ASP.NET Core Web API project.

Step 2: Install IronPDF

Crystal Reports requires you to install a heavy runtime engine on every server. IronPDF is different; it is just a library you add to your project.

  • Go to Tools > NuGet Package Manager > Package Manager Console.
  • Run the following command:
Install-Package IronPdf

This will download the IronPDF engine and all necessary dependencies (like System.Drawing.Common) into your project.

Alternative Approach: Install IronPDF Using the NuGet Package Browser

If you prefer a graphical interface instead of typing console commands, you can install IronPDF through the NuGet Package Manager UI.

  1. Right-click your project in Solution Explorer then Select Manage NuGet Packages
  2. Go to the Browse tab.
  3. In the search box, type: IronPdf
  4. Select the package named IronPdf (published by Iron Software).
  5. On the right side, choose the version you want (usually the latest stable).
  6. Click Install.

Press enter or click to view image in full size

Installing IronPDF from the NuGet Package Manager in Visual Studio.

Step 3: Setup Configuration (The JSON File)

ASP.NET Core already includes a built-in configuration system that loads settings from appsettings.json automatically.

we’ll use appsettings.json to store sensitive data like the default report password.

Get Rafay Khan’s stories in your inbox

Join Medium for free to get updates from this writer.

Locate your appsettings.json file and add a new section. This will house the security settings we use for encryption:

"ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=DAW_TST;user id = sa;password=anypass!;MultipleActiveResultSets=True;TrustServerCertificate=True",},"ReportSecurity": { "UserPassword": "MySecurePassword123!", "OwnerPassword": "AdminSecretOwnerPass"}

Add this under the root object — you can keep existing sections like Logging, ConnectionStrings, etc.

Step 4: Define Models

Create the Models folder for data transfer. Create the Model (Models/ResultModel.cs)

Create a new model class named ResultModel.cs inside the Models folder to represent the API response data.

Step 5: Add TemplateHelper Utility

We create TemplateHelper to dynamically fill HTML templates with real data before generating a PDF. It replaces placeholder values like {{Name}} or {{Date}} with actual values and removes unused placeholders to keep the final HTML clean. (Helper/TemplateHelper.cs)

Reusable helper for filling HTML templates and cleaning leftover placeholders.

Because we’re pulling data from the database (often through stored procedures), we need a clean way to map that data into the HTML template used for PDF generation.

  1. TemplateHelper.csLooks Like:
using System.Text.RegularExpressions;namespace SecureReportApi.Helper{ public static class TemplateHelper { public static string ApplyDataToTemplate(string htmlTemplate, IDictionary<string, object> data) { if (string.IsNullOrEmpty(htmlTemplate)) return string.Empty; string processedHtml = htmlTemplate; // Iterate through the data dictionary to replace specific keys if (data != null) { foreach (var kvp in data) { string key = kvp.Key; // specific check to match JS: data[key] !== null && data[key] !== undefined string value = kvp.Value != null ? kvp.Value.ToString() : ""; // Replace {{key}} with the value.  // String.Replace in C# replaces ALL occurrences (equivalent to JS global regex 'g') processedHtml = processedHtml.Replace("{{" + key + "}}", value); } } // Remove any remaining unreplaced placeholders // Regex pattern matches "{{" followed by any character (non-greedy) until "}}" processedHtml = Regex.Replace(processedHtml, "{{.*?}}", ""); return processedHtml; } }}

Step 6: Data Retrieval in the Service Layer

Now Creating a IReportServiceand ReportService is responsible for fetching the necessary invoice details from the database. This logic is already robust, using ADO.NET (SqlCommand and DataTable) to execute a stored procedure (usp_APIGetinvoices).

IReportService.cs and ReportService.cs for handling report-generation logic.
  1. IReportService.csLooks Like:
using SecureReportApi.Models;namespace SecureReportApi.Services{ interface IReportService { public Task GetReport(string invoiceNumber); }}
  • IReportService is an interface, defining a contract for report operations without tying to a specific implementation.

Why create it:

  • Enables loose coupling and dependency injection.
  • Makes unit testing easier (you can mock the interface).
  • Allows multiple implementations (e.g., PDF).
  • Task is async, suitable for I/O operations like database or HTTP calls.
  • Correction: remove public from interface methods — they are implicitly public.

Using Helper Method to fetch data from Database Stored Procedures or Query

 #region Database  private async Task GetDataTableAsync(string query, params (string name, SqlDbType type, object value)[] parameters) { var dataTable = new DataTable(); await using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); await using var command = new SqlCommand(query, connection); AddParameters(command, parameters); await using var reader = await command.ExecuteReaderAsync(); dataTable.Load(reader); return dataTable; } private static void AddParameters(SqlCommand command, (string name, SqlDbType type, object value)[] parameters) { foreach (var (name, type, value) in parameters) command.Parameters.Add(name, type).Value = value; } #endregion

This code snippet is a handy async method to fetch data into a DataTable while preventing SQL injection via parameters. It’s wrapped in a #region for organization in your IDE.

The Main Method: GetDataTableAsync()

  • This async function runs a SQL query and returns results as a DataTable (like an in-memory table).
  • Inputs: A query string (e.g., “SELECT * FROM Users WHERE Id = @Id”) and optional parameters (as tuples: name, type, value).

What it does:

  1. Creates an empty DataTable.
  2. Opens a connection to the database using a connection string.
  3. Sets up a SqlCommand with the query.
  4. Adds parameters (if any) to avoid injection risks.
  5. Executes the query and reads results asynchronously.
  6. Loads the data into the DataTable and returns it.

It uses await using for auto-cleanup of resources like connections and readers.

The Helper Method: AddParameters()

A simple static method that loops through the parameter tuples and adds them to the command. Each param is like @Id (name), SqlDbType.Int (type), and 5 (value).

Crafting a Printable Commercial Invoice with HTML and CSS

Report Header:

<div style='font-family: Arial, sans-serif; border: 1px solid #ccc; padding: 20px;'> <h2 style='color: #333;'>Commercial Invoice: {invoiceNo}h2> <p><strong>Manufacture:strong> {manufacture}p> <p><strong>Customer:strong> {customer}p> <p><strong>Shipping To:strong> {city}, {country}p> div> <h3 style='margin-top: 20px;'>Line Items:h3> <table style='width: 100%; border-collapse: collapse; margin-top: 10px;'> <thead> <tr style='background-color: #f2f2f2;'> <th style='border: 1px solid #ccc; padding: 8px; text-align: left;'>Productth> <th style='border: 1px solid #ccc; padding: 8px; text-align: left;'>Item Codeth> <th style='border: 1px solid #ccc; padding: 8px; text-align: left;'>Lot Numberth> <th style='border: 1px solid #ccc; padding: 8px; text-align: left;'>Qtyth> <th style='border: 1px solid #ccc; padding: 8px; text-align: left;'>Prod/Exp Datesth> tr> thead><tbody>

Dynamically Populating the Invoice Table with C# Templates:

<tr> <td style='border: 1px solid #ccc; padding: 8px;'>{{ItemDescription}}td> <td style='border: 1px solid #ccc; padding: 8px;'>{{ItemCode}}td> <td style='border: 1px solid #ccc; padding: 8px;'>{{LotNumber}}td> <td style='border: 1px solid #ccc; padding: 8px;'>{{Qty}} {{QtyUnit}}td> <td style='border: 1px solid #ccc; padding: 8px;'>{{ProductionDate}} / {{ExpireDate}}td>tr>

IronPDF — a powerful library for .NET that handles PDF creation and protection seamlessly. Below snippet shows how to render HTML to PDF, slap on password protection, and control user permissions like printing and copying.

var renderer = new ChromePdfRenderer();var pdf = renderer.RenderHtmlAsPdf(finalHtml);// Add password protectionpdf.SecuritySettings.OwnerPassword = _configuration["ReportSecurity:OwnerPassword"];pdf.SecuritySettings.UserPassword = _configuration["ReportSecurity:UserPassword"];pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights;pdf.SecuritySettings.AllowUserCopyPasteContent = false;// Save the password-protected PDFpdf.SaveAs("protected-report.pdf");
  1. ReportService.csLooks Like:
using Microsoft.Data.SqlClient;using SecureReportApi.Helper;using SecureReportApi.Models;using System.Data;namespace SecureReportApi.Services{ public class ReportService:IReportService { private readonly string _connectionString; private readonly IConfiguration _configuration; public ReportService(IConfiguration configuration) { _configuration = configuration; _connectionString = _configuration["ConnectionStrings:DefaultConnection"] ?? throw new InvalidOperationException("Connection string not found"); } public async Task GetReport(string invoiceNumber) { ResultModel _resultModel = new ResultModel(); try { string query = $"EXEC usp_APIGetinvoices '{invoiceNumber}"; var salesInvoices = await GetDataTableAsync(query); if (salesInvoices.Rows == null || salesInvoices.Rows.Count <= 0) { _resultModel.Message = "Records Not Found"; return _resultModel; } // Get header data (assuming all rows share these common fields) var headerRow = salesInvoices.Rows[0]; string invoiceNo = headerRow["InvoiceNo"]?.ToString() ?? "N/A"; string customer = headerRow["Customer"]?.ToString() ?? "N/A"; string manufacture = headerRow["Manufacture"]?.ToString() ?? "N/A"; string country = headerRow["Country"]?.ToString() ?? "N/A"; string city = headerRow["City"]?.ToString() ?? "N/A"; string headerHtml = $@" 

Commercial Invoice: {invoiceNo}

Manufacture: {manufacture}

Customer: {customer}

Shipping To: {city}, {country}

Line Items:

"
Product Item Code Lot Number Qty Prod/Exp Dates
; // Template for each line item row string itemTemplate = @" {{ItemDescription}} {{ItemCode}} {{LotNumber}} {{Qty}} {{QtyUnit}} {{ProductionDate}} / {{ExpireDate}} "; var generatedHtmlList = new List<string>(); foreach (System.Data.DataRow row in salesInvoices.Rows) { var rowData = new Dictionary<string, object>(); foreach (System.Data.DataColumn col in salesInvoices.Columns) { // Convert DBNull to null or empty string to avoid errors rowData[col.ColumnName] = row[col] == DBNull.Value ? null : row[col]; } // 4. Apply the item template for each row string populatedHtml = TemplateHelper.ApplyDataToTemplate(itemTemplate, rowData); generatedHtmlList.Add(populatedHtml); } // 5. Join the list of item rows string itemRowsHtml = string.Join(Environment.NewLine, generatedHtmlList); // 6. Assemble the final HTML: Header + Item Rows + Table End Tag string finalHtml = headerHtml + itemRowsHtml + ""; // 7. Generate PDF from HTML (great for reports) var renderer = new ChromePdfRenderer(); var pdf = renderer.RenderHtmlAsPdf(finalHtml); // Add password protection pdf.SecuritySettings.OwnerPassword = _configuration["ReportSecurity:OwnerPassword"]; pdf.SecuritySettings.UserPassword = _configuration["ReportSecurity:UserPassword"]; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; pdf.SecuritySettings.AllowUserCopyPasteContent = false; // Save the password-protected PDF pdf.SaveAs("protected-report.pdf"); _resultModel.Success = true; _resultModel.Message = "Operation Completed Successfully"; } catch (Exception ex) { _resultModel.Success = false; _resultModel.Message = "Server Error: " + ex.Message; } return _resultModel; } #region Database private async Task GetDataTableAsync(string query, params (string name, SqlDbType type, object value)[] parameters) { var dataTable = new DataTable(); await using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); await using var command = new SqlCommand(query, connection); AddParameters(command, parameters); await using var reader = await command.ExecuteReaderAsync(); dataTable.Load(reader); return dataTable; } private static void AddParameters(SqlCommand command, (string name, SqlDbType type, object value)[] parameters) { foreach (var (name, type, value) in parameters) command.Parameters.Add(name, type).Value = value; } #endregion }}

Before Creating Endpoint we need to register our IReportService inside program.cs

This promotes separation of concerns the controller doesn’t know how the report is generated.

3. Register the Configuration in Program.cs

we’ve already covered fetching data from the database, templating HTML, and rendering protected PDFs with IronPDF. Now, it’s time to tie it all together with a dedicated controller.

Enter the ReportControllera clean, injectable class that exposes a simple GET endpoint to generate a report by invoice number. This keeps your API RESTful, secure, and easy to scale. We’ll use dependency injection for the service layer, ensuring loose coupling and testability.

Controller Code for Generate Pdf:

using Microsoft.AspNetCore.Mvc;using SecureReportApi.Services;namespace SecureReportApi.Controllers{ [Route("api/[controller]")] [ApiController] public class ReportController : ControllerBase { private readonly IReportService _reportService; public ReportController(IReportService reportService) { _reportService = reportService; } // GET: api/ [HttpGet] public async Task Get(string invoiceNumber) { var result = await _reportService.GetReport(invoiceNumber); return Ok(result); } }}

Alternative Approach: Streaming PDFs Directly from Your ASP.NET Core API Endpoint

Make little modification inside ReportController.cs

Press enter or click to view image in full size

This code snippet from an ASP.NET Core ReportController demonstrates an asynchronous GET method that fetches a report using an invoice number. If successful, it reads and returns a static PDF file named “protected-report.pdf”; otherwise, it sends a bad request response. Ideal for illustrating simple file serving in web APIs.

Press enter or click to view image in full size

Testing the Report API Endpoint: Successful Retrieval of Protected PDF via GET Request

Here is project Link:

GitHub – rafaykhanzada/IronPdfWithDataBase

Contribute to rafaykhanzada/IronPdfWithDataBase development by creating an account on GitHub.

github.com

Wrapping Up

This code snippet from an ASP.NET Core ReportController demonstrates an asynchronous GET method that fetches a report using an invoice number. If successful, it reads and returns a static PDF file named “protected-report.pdf”; otherwise, it sends a bad request response. Ideal for illustrating simple file serving in web APIs.

Information contained on this page is provided by an independent third-party content provider. Frankly and this Site make no warranties or representations in connection therewith. If you are affiliated with this page and would like it removed please contact [email protected]