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.
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 deniederror.
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).
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:
- Add PdfPig NuGet package
- Create
PdfServiceclass - Declare method with byte array parameters
- Create output MemoryStream
- 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
.csprojfile to prevent unexpected breaking changes.
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[]
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
outputStreamas aMemoryStreamto receive merged PDF bytes. - Step 2 - Create
builderas aPdfDocumentBuilderthat writes intooutputStream.
- Step 1 - Create
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[]usingoutputStream.ToArray().
- Step 1 - Add both input PDFs to the builder using
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(...)
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
.csprojfile to prevent unexpected breaking changes during automated build updates. - Keep it In-Memory: Processing documents via
byte[]arrays andMemoryStreambypasses 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!!!