Fixing PDFsharp Access is denied Errors in IIS by Migrating to PdfPig

Objective of this article is simple, answer the below question.

Question: How to resolve PDF manipulation failures in a .NET API deployed on IIS.



The Problem

While implementing PDF features inside an enterprise API, the deployed IIS server throws the error shown below.

System.IO.FileLoadException: Could not load file or assembly 'PdfSharp.dll'. Access is denied.

What makes this problem frustrating is its inconsistent behavior.

  • It works perfectly on the local development machine.
  • Other libraries inside the same published API folder load and work without any issue.
  • Sometimes, after an application pool recycle, PDFsharp randomly starts working, only to crash again later.
Why is it inconsistent?

This inconsistency is not random. It is caused by how IIS security interacts with the Windows desktop graphics subsystem.



Root Cause

The instability comes from architectural legacy inside traditional PDFsharp.

Traditional versions of PDFsharp rely heavily on native Windows desktop subsystems — specifically GDI+ or WPF — to handle text layouts, fonts, and graphics rendering.

When hosted inside IIS, two problems occur.

Problem 1: Strict Security Boundaries

IIS application pools run under highly restricted, low-privilege identities (like ApplicationPoolIdentity) by default.

These identities are blocked from accessing:

  • System-level graphics pipelines
  • Windows Font Cache

Problem 2: Race Conditions

  • If a high-privilege application pool initializes the GDI graphics subsystem first after a server reboot, your API can piggyback on that active font cache and work.
  • If your restricted API spins up first, it lacks the OS rights to build that cache, triggering an immediate Access is denied error.
Important

This is why the error appears randomly. The result depends on which application pool initializes first after a server restart.



Solution Strategy

Because modern .NET runtimes do not have native built-in libraries to parse and merge PDF binaries, a third-party library is mandatory.

However, to survive tight IIS security, the library must operate entirely in managed C# memory without requesting low-level Windows OS hooks.

The ideal open-source, MIT-licensed alternative is PdfPig (UglyToad.PdfPig).

Why PdfPig works

Unlike legacy frameworks, PdfPig reads and reconstructs PDF cross-reference tables directly inside the .NET runtime memory space. If your API has enough permissions to do basic string operations, it has enough permissions to run PdfPig.


Implementation

Below is the implementation code using PdfPig.

using System.IO;
using UglyToad.PdfPig.Writer;

public class PdfService
{
    public byte[] MergeAndReplacePdfPages(byte[] originalPdfBytes, byte[] replacementPagePdfBytes)
    {
        using (var outputStream = new MemoryStream())
        {
            var builder = new PdfDocumentBuilder(outputStream);

            // Feed the raw byte arrays straight into the memory parser
            builder.AddPdfDocument(new MemoryStream(originalPdfBytes));
            builder.AddPdfDocument(new MemoryStream(replacementPagePdfBytes));

            // Compile the binary mappings and cross-reference tables automatically
            builder.Build();

            return outputStream.ToArray();
        }
    }
}

Steps To Follow

This implementation can be divided into following sections:

  1. Add PdfPig NuGet package
  2. Create PdfService class
  3. Declare method with byte array parameters
  4. Create output MemoryStream
  5. Build merged PDF and return bytes

Every section with each line is explained below.

Add PdfPig NuGet Package

Install the package via NuGet Package Manager or run the command below.

dotnet add package UglyToad.PdfPig
  • Purpose: Adds PdfPig as a dependency to your project.
  • Note: Pin the version explicitly in your .csproj file to prevent unexpected breaking changes.
Pin the Version

PdfPig uses rapid minor versioning (e.g., 0.1.x). Pin the exact version in your .csproj file using <PackageReference Include="UglyToad.PdfPig" Version="0.1.16" />.

Create PdfService Class

public class PdfService
{

}
  • Purpose: Defines the service class that contains PDF manipulation logic.
  • Class Name: PdfService

Declare Method With Byte Array Parameters

public byte[] MergeAndReplacePdfPages(byte[] originalPdfBytes, byte[] replacementPagePdfBytes)
  • Purpose: Declares the method that merges two PDF documents.
  • Return Type: byte[]
  • Parameter 1 Name: originalPdfBytes
  • Parameter 1 Type: byte[]
  • Parameter 2 Name: replacementPagePdfBytes
  • Parameter 2 Type: byte[]
Keep it In-Memory

Processing documents via byte[] arrays and MemoryStream objects bypasses local disk write permission issues on highly secured production servers.

Create Output MemoryStream

using (var outputStream = new MemoryStream())
{
    var builder = new PdfDocumentBuilder(outputStream);
  • In above lines, we do 2 steps.

    • Step 1 - Create outputStream as a MemoryStream to receive merged PDF bytes.
    • Step 2 - Create builder as a PdfDocumentBuilder that writes into outputStream.

Build Merged PDF And Return Bytes

// Feed the raw byte arrays straight into the memory parser
builder.AddPdfDocument(new MemoryStream(originalPdfBytes));
builder.AddPdfDocument(new MemoryStream(replacementPagePdfBytes));

// Compile the binary mappings and cross-reference tables automatically
builder.Build();

return outputStream.ToArray();
  • In above lines, we do 3 steps.

    • Step 1 - Add both input PDFs to the builder using AddPdfDocument.
    • Step 2 - Call builder.Build() to compile the binary mappings and cross-reference tables.
    • Step 3 - Return the final merged PDF as a byte[] using outputStream.ToArray().


Deployment Gotcha: FileNotFoundException

After changing to PdfPig, a new error may appear on the next deployment.

System.IO.FileNotFoundException: Could not load file or assembly 'UglyToad.PdfPig, Version=0.1.16.0...'. The system cannot find the file specified.
   at Application.Services.PdfService.MergeAndReplacePdfPages(...)
Important

This error means the code logic is correct, but the physical file UglyToad.PdfPig.dll was not copied to the server during deployment.

How to Fix the Deployment File Gap

Follow these steps to resolve the missing DLL issue.

Step 1 — Clean the build space.

Delete the local bin and obj folders to force .NET to regenerate clean dependency maps.

Step 2 — Configure Copy Local.

Ensure the NuGet package properties for UglyToad.PdfPig have Copy Local set to True.

Step 3 — Purge the IIS folder.

In your Visual Studio Publish Settings or your DevOps YAML pipeline, enable the option to Delete all existing files prior to publish.

This ensures a completely clean deployment and forces the .deps.json file to update.

Step 4 — Manual verification.

If using a manual copy-paste deployment, copy UglyToad.PdfPig.dll directly from your local bin\Release\net10.0\ folder and paste it into the server’s root API folder.



Key Takeaways

Key Takeaways
  • Avoid Desktop Engines in Web APIs: Never use libraries dependent on GDI+ or WPF inside hosted web environments like IIS or Azure Web Apps.
  • Pin Dependency Versions: Explicitly pin the package version in your .csproj file to prevent unexpected breaking changes during automated build updates.
  • Keep it In-Memory: Processing documents via byte[] arrays and MemoryStream bypasses local disk write permission issues on highly secured production servers.

This is it !!!

I hope my notes are helpful to someone! 😊

If you found anything to add or update, please let me know on my e-mail 📧.

Hope this post helps you to resolve PDF manipulation failures when deploying .NET APIs on IIS.

If you like the post then please share it with your friends also. 🙏🏻

Do let me know if you like this post or not!

Till then, Happy coding!!!