Print to PDF from ASP.NET handler / PDF Printer
It is very common that we hear from VB.NET or C# programmers that they want to create PDF documents from ASP.NET applications. This guide will show you how this can be done.
Working with ASP.NET running under IIS, it can often be a challenge to handle the security. This is also an important issue when you want to print a PDF document and stream it to the user. Hopefully, the following will help you overcome these challenges and put you on track with a good PDF solution for your ASP.NET application.
At the end of this guide, you will find a link to the source code used to build this example.
What Does the Example Do?
The example is an ASP.NET handler that creates and streams a PDF document to the web site visitor. The PDF code can be placed in a normal ASP.NET form as well as this handler.
Print from ASP.NET using the PrintDocument class
This example will focus on printing from C# using the PrintDocument class and the PrintPageEventHandler. This is the typical way that you incorporate printing in your Microsoft.NET application. Even though the example is in C#, the principles should also apply to VB.NET or any other .NET compatible language.
The Challenges
When we want to print to PDF from ASP.NET we run into a couple of challenges. The first one is the security surrounding IIS and the second one is concurrency.
Printing and SecurityIn a normal IIS installation, the user context is locked down to serving files and running scripts. This example was built to run with default security settings. This means that you do not have to extend the existing permissions for the IIS user to make this work. Doing this could pose a security risk for your server and we really do not want that.
Normally, the PDF printer uses the user context of the printing user to perform the PDF creation. This makes a lot of sense when you use it from the desktop or a Terminal Server. Even if you use it from a normal service, it can be a benefit to run the conversion in the context of the service user. However, when we look at the IIS the user context is too locked down. Your visitors from the web site may be anonymous and therefore we cannot rely on the normal security context.
Luckily, the PDF printer can be configured to run in the security context of the Print Spooler service. This service normally uses the System account to log in. The System account has access to most local resources such as local hard drives. In order to configure the printer to run in the Spooler context, a number of registry settings should be made. Instead of having to do this manually, the printer setup program has a command line switch that tells it to install a printer and prepare it for use with IIS.
Printing and ConcurrencyWhen a user visits your web site and want's to receive a PDF document, you start a process that produces the requested document. Another user could come to your web site and request another PDF while the first one is still being produced.
Normally the printer is controlled by creating a runonce.ini configuration file. This file sets parameters for creation of the next PDF print job. With concurrent users, this way of printing cannot be used because one runonce.ini may overwrite another that has not been processed yet by the printer. Therefore, we have to use a feature in the PDF printer that allows us to tell the printer exactly which runonce configuration to use for a specific print job.
Running the Example
Here is how you install the printer and setup the ASP.NET application.
Installing the PDF PrinterThe first step to making this example run is to install a PDF printer and prepare it for use with IIS. Remember that you need to use version 10.8 or later for this to work. Even if you have the printer installed already, I recommend that you install this new instance of the printer and name it for use with IIS. To complete these tasks you simply run the following command line in a folder where you have the setup program for the printer:
Setup_7PDF_10_8_0_2277_PRO_EXP.exe /SPOOLERCONTEXT /SILENT /PRINTERNAME="IIS PDF Printer"
This will set the correct registry entries create a folder structure for files related to the PDF creation. By default, these files and folders are created here:
C:\ProgramData\PDF Writer\IIS PDF Printer
You need to download the example files and register the folder as an IIS application with support for ASP.NET. Remember to make the APP_DATA folder writable for the APS.NET application user.
When the application is created and the printer is installed you can browse to Default.aspx and try the example.
The Source Code
You can download the source code for this example in the ZIP file below or get inspired by the source code listing.
<%@ WebHandler Language="C#" Class="Print" %> using System; using System.Web; using System.Collections.Generic; using System.Drawing; using System.Drawing.Printing; using System.IO; using System.Linq; using pdf7.PdfWriter; public class Print : IHttpHandler { public void ProcessRequest(HttpContext context) { string printerName = "IIS PDF Printer"; string downloadName = "aspnet test.pdf"; string jobId = Guid.NewGuid().ToString(); string jobName = string.Format("ASP.NET-{0}", jobId); string tempPath = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data"), "Temp"); string jobTempPath = Path.Combine(tempPath, jobId); string pdfName = string.Format("ASP.NET-{0}.pdf", jobId); string pdfPath = Path.Combine(jobTempPath, pdfName); string statusFile = Path.Combine(jobTempPath, "status.ini"); // Create folders Directory.CreateDirectory(jobTempPath); // Set parameters for print job PdfSettings pdfSettings = new PdfSettings(); pdfSettings.PrinterName = printerName; // These settings will only have effect if they are not overwritten by values in // the global.ini pdfSettings.SetValue("Output", pdfPath); pdfSettings.SetValue("StatusFile", statusFile); pdfSettings.SetValue("ShowSettings", "never"); pdfSettings.SetValue("ShowSaveAS", "never"); pdfSettings.SetValue("ShowProgress", "no"); pdfSettings.SetValue("ShowProgressFinished", "no"); pdfSettings.SetValue("ShowPDF", "no"); pdfSettings.SetValue("ConfirmOverwrite", "no"); pdfSettings.SetValue("RememberLastFolderName", "no"); pdfSettings.SetValue("RememberLastFileName", "no"); pdfSettings.SetValue("WatermarkLayer", "bottom"); pdfSettings.SetValue("WatermarkText", "Test from ASP.NET"); // Create a runonce.ini that is specific to this job // https://www.7-pdf.com/products/pdf-printer/documentation/settings string defaultRunoncePath = pdfSettings.GetSettingsFilePath(PdfSettingsFileType.RunOnce); string specificRunoncePath = Path.Combine( Path.GetDirectoryName(defaultRunoncePath), string.Format("runonce_{0}.ini", Uri.EscapeDataString(jobName))); pdfSettings.WriteSettings(specificRunoncePath); // Create print job PrintDocument pd = new PrintDocument(); pd.DocumentName = jobName; pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage); pd.PrinterSettings.PrinterName = printerName; pd.DefaultPageSettings.Landscape = false; pd.DefaultPageSettings.PaperSize = new PaperSize("Letter", 850, 1100); pd.Print(); // Wait for PDF creation to finish if (PdfUtil.WaitForFile(statusFile, 20000)) { // Read error information from status file string errorValue = PdfUtil.ReadIniString(statusFile, "Status", "Errors", ""); if (errorValue == "0") { // Stream PDF to browser context.Response.ClearContent(); context.Response.ContentType = "Application/pdf"; context.Response.AddHeader("Content-Disposition", "inline; filename=" + downloadName); context.Response.WriteFile(pdfPath); context.Response.Flush(); // Remove files after the PDF is streamed to the client browser File.Delete(pdfPath); File.Delete(statusFile); Directory.Delete(jobTempPath); context.Response.End(); return; } else { string errorMessage = PdfUtil.ReadIniString(statusFile, "Status", "MessageText", ""); WriteErrorMessage(string.Format("An error was reported: {0}; {1}", errorValue, errorMessage)); } } WriteErrorMessage("No PDF was created."); } private void WriteErrorMessage(string message) { HttpContext.Current.Response.ContentType = "text/plain"; HttpContext.Current.Response.Write(message); } private void pd_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { // Output something in the print SolidBrush myBrush = new SolidBrush(Color.Blue); Font font = new Font("Arial", 12); e.Graphics.DrawString("Print test: " + DateTime.Now.ToString(), font, myBrush, e.MarginBounds.Left, e.MarginBounds.Top); myBrush.Dispose(); } public bool IsReusable { get { return false; } } }
Trouble Shooting
If you have not set the write access for the IIS user or users in general on the APP_DATA folder, you will see an error similar to this: Access to the path 'C:\inetpub\Print From Handler\App_Data\Temp\b2c27634-1ee5-4afc-a4da-0f93c7b2452f' is denied.
You can download and run the example yourself. The files needed are available here.
Downloads
Attachment | Size |
---|---|
Example file | 8.7 KB |