<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chloe</title>
    <description>The latest articles on DEV Community by Chloe (@jenll).</description>
    <link>https://dev.to/jenll</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3826848%2Fc566bd52-364f-45f1-b4ca-91b1ef8efd21.png</url>
      <title>DEV Community: Chloe</title>
      <link>https://dev.to/jenll</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jenll"/>
    <language>en</language>
    <item>
      <title>Why Excel Interop Is a Bad Idea for ASP.NET Applications</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 11 May 2026 07:38:53 +0000</pubDate>
      <link>https://dev.to/jenll/why-excel-interop-is-a-bad-idea-for-aspnet-applications-45on</link>
      <guid>https://dev.to/jenll/why-excel-interop-is-a-bad-idea-for-aspnet-applications-45on</guid>
      <description>&lt;p&gt;&lt;em&gt;What breaks in production — and what developers use instead&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It usually starts with a simple requirement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Upload an Excel file, process it on the server, and export a report."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you reach for &lt;code&gt;Microsoft.Office.Interop.Excel&lt;/code&gt;. It's familiar. It's part of the Microsoft ecosystem. And it works — at least on your machine.&lt;/p&gt;

&lt;p&gt;You open a workbook. You read a few cells. You write a test. Everything passes.&lt;/p&gt;

&lt;p&gt;Then you deploy to production.&lt;/p&gt;

&lt;p&gt;Within days, you start noticing things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excel processes quietly pile up in Task Manager, never exiting&lt;/li&gt;
&lt;li&gt;Some requests hang indefinitely with no error, no timeout, no explanation&lt;/li&gt;
&lt;li&gt;IIS recycles unexpectedly under moderate load&lt;/li&gt;
&lt;li&gt;A background job that worked fine in staging fails intermittently&lt;/li&gt;
&lt;li&gt;Deploying to a new server means installing Microsoft Office — and hoping the license key is somewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these failures are dramatic at first. They're subtle. They show up gradually. And by the time the pattern is obvious, they're already embedded in production infrastructure.&lt;/p&gt;

&lt;p&gt;This is the Excel Interop trap. It looks like a solution. The API feels simple — until it becomes part of your production infrastructure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. What Is Excel Interop?&lt;/li&gt;
&lt;li&gt;
2. Why Excel Interop Fails on Servers

&lt;ul&gt;
&lt;li&gt;2.1 Microsoft Does Not Recommend It&lt;/li&gt;
&lt;li&gt;2.2 Excel Requires a Desktop Environment&lt;/li&gt;
&lt;li&gt;2.3 COM Objects Cause Memory Leaks&lt;/li&gt;
&lt;li&gt;2.4 Not Thread-Safe&lt;/li&gt;
&lt;li&gt;2.5 Incompatible with Modern Deployments&lt;/li&gt;
&lt;li&gt;2.6 Security Concerns&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;3. What Developers Use Instead&lt;/li&gt;

&lt;li&gt;4. Example: Reading Excel Files Without Interop&lt;/li&gt;

&lt;li&gt;5. When Interop Still Makes Sense&lt;/li&gt;

&lt;li&gt;6. How to Migrate Away from Interop&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a id="1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What Is Excel Interop?
&lt;/h2&gt;

&lt;p&gt;Excel Interop is part of the Microsoft Office automation API, exposed through the &lt;code&gt;Microsoft.Office.Interop.Excel&lt;/code&gt; namespace. It lets C# code control Excel the same way a human user would — opening workbooks, reading and writing cells, triggering formulas, running macros, and saving files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="n"&gt;excel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Workbooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\reports\data.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worksheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, it works through COM Automation. Your .NET process doesn't parse the Excel file format directly — it launches a real Excel instance and sends instructions to it over the COM interface. Excel is running. It has a window (even if hidden). It manages its own memory, its own file handles, and its own process lifetime.&lt;/p&gt;

&lt;p&gt;For desktop automation — a local tool that an individual runs interactively — this can work reasonably well.&lt;/p&gt;

&lt;p&gt;For ASP.NET applications and server environments, it's a very different story.&lt;/p&gt;

&lt;p&gt;&lt;a id="2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why Excel Interop Fails on Servers
&lt;/h2&gt;

&lt;p&gt;The problems aren't edge cases. They're structural — and they compound each other in production.&lt;/p&gt;

&lt;p&gt;&lt;a id="2-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Microsoft Does Not Recommend It
&lt;/h3&gt;

&lt;p&gt;This isn't opinion. &lt;a href="https://support.microsoft.com/en-us/topic/considerations-for-server-side-automation-of-office-48bcfe93-8a89-47f1-0bce-017433ad79e2" rel="noopener noreferrer"&gt;Microsoft has explicitly stated&lt;/a&gt; that Office Automation is unsupported in unattended server environments, including ASP.NET and Windows Services.&lt;/p&gt;

&lt;p&gt;Office applications were built for interactive desktop use. When something breaks in production, you're not debugging a bug — you're operating outside the supported boundaries of the software itself.&lt;/p&gt;

&lt;p&gt;&lt;a id="2-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Excel Requires a Desktop Environment
&lt;/h3&gt;

&lt;p&gt;Calling &lt;code&gt;new Application()&lt;/code&gt; doesn't open a file. It launches a full Excel process — with GUI infrastructure, user profile dependencies, and desktop session requirements that don't reliably exist on a server.&lt;/p&gt;

&lt;p&gt;A Windows update, a Group Policy change, or a permission tweak can silently break everything overnight. The error messages are vague. The fix is never obvious.&lt;/p&gt;

&lt;p&gt;&lt;a id="2-3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 COM Objects Cause Memory Leaks
&lt;/h3&gt;

&lt;p&gt;Every Excel object you touch is a &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.releasecomobject" rel="noopener noreferrer"&gt;COM object&lt;/a&gt; that the .NET GC can't clean up on its own. Miss one &lt;code&gt;Marshal.ReleaseComObject&lt;/code&gt; call — in an exception handler, inside a loop, in a helper method — and you get orphaned &lt;code&gt;EXCEL.EXE&lt;/code&gt; processes that never exit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You have to do this manually. For every object. Every time.&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over hours, these processes accumulate. Memory climbs. Files stay locked. Someone RDPs in and kills processes by hand.&lt;/p&gt;

&lt;p&gt;If your production runbook includes "check for orphaned Excel processes," that's a sign something is fundamentally wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6pgbn9h4wp2unj6ergy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6pgbn9h4wp2unj6ergy.webp" alt="Multiple EXCEL processes in Task Manager" width="750" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id="2-4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Not Thread-Safe
&lt;/h3&gt;

&lt;p&gt;ASP.NET processes requests concurrently. Excel Interop uses a &lt;a href="https://learn.microsoft.com/en-us/archive/blogs/alikl/pitfalls-with-net-multithreading-and-com-objects-threads-must-have-compatible-apartment-models-mta-vs-sta" rel="noopener noreferrer"&gt;Single-Threaded Apartment (STA) model&lt;/a&gt; and was never designed for concurrent access.&lt;/p&gt;

&lt;p&gt;Under load, this means deadlocks, hanging requests, and IIS process recycling. You can add manual locking to serialize access — but then you've just built a bottleneck on top of a fragile foundation.&lt;/p&gt;

&lt;p&gt;&lt;a id="2-5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.5 Incompatible with Modern Deployments
&lt;/h3&gt;

&lt;p&gt;Excel Interop requires Windows, a full Office installation, and a valid license on every machine that runs your code — including CI/CD agents, staging servers, and auto-scaled instances.&lt;/p&gt;

&lt;p&gt;There's no Docker image for this. A surprising number of modernization projects get blocked by a single Excel dependency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiocj6ch9lv0zpz301ci6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiocj6ch9lv0zpz301ci6.webp" alt="Docker and Excel Interop incompatibility diagram" width="750" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a id="2-6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 Security Concerns
&lt;/h3&gt;

&lt;p&gt;Three risks that are easy to overlook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Macro and DDE execution&lt;/strong&gt; — Excel may run embedded macros or DDE links in uploaded files, opening a code execution path on your server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overprivileged process&lt;/strong&gt; — the Excel process inherits your app pool identity, widening the blast radius of a malicious file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temp file leakage&lt;/strong&gt; — Excel writes AutoRecover and clipboard data to disk; in shared environments, this data can persist longer than expected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What Developers Use Instead
&lt;/h2&gt;

&lt;p&gt;The important shift is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modern libraries work with the Excel file format itself — not the Excel desktop application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means no COM automation, no hidden Excel processes, and no Office installation on the server.&lt;/p&gt;

&lt;p&gt;The good news is that the .NET ecosystem already has several mature options for this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Excel Interop&lt;/th&gt;
&lt;th&gt;Modern Libraries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requires Microsoft Office&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs in Docker/Linux&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;COM Cleanup Needed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Designed for Server Use&lt;/td&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background Excel Processes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's a practical overview of the common ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open XML SDK
&lt;/h3&gt;

&lt;p&gt;Microsoft's official open-source library for reading and writing Office file formats. It gives you direct access to the underlying XML structure of &lt;code&gt;.xlsx&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; developers who want full control over document structure and don't mind a lower-level API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth knowing:&lt;/strong&gt; the API is verbose. Simple operations — reading a cell value, writing a row — require significantly more code than you'd expect. For complex spreadsheets, it can become difficult to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  EPPlus
&lt;/h3&gt;

&lt;p&gt;One of the most widely used Excel libraries in the .NET ecosystem. The API is intuitive, the documentation is solid, and it handles most common spreadsheet operations cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; teams that need a productive API and are comfortable with its licensing model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth knowing:&lt;/strong&gt; &lt;a href="https://epplussoftware.com/" rel="noopener noreferrer"&gt;EPPlus&lt;/a&gt; moved to a commercial license for business use starting with version 5. It's still free for non-commercial projects, but production use in a commercial application requires a paid license.&lt;/p&gt;

&lt;h3&gt;
  
  
  NPOI
&lt;/h3&gt;

&lt;p&gt;A .NET port of the Java Apache POI library. It's free, open-source, and supports both &lt;code&gt;.xls&lt;/code&gt; and &lt;code&gt;.xlsx&lt;/code&gt; formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; teams migrating older codebases, or projects that need to support legacy &lt;code&gt;.xls&lt;/code&gt; files alongside modern &lt;code&gt;.xlsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth knowing:&lt;/strong&gt; the API reflects its Java origins — it works, but it doesn't feel native to C#. Community activity has slowed in recent years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Office-Independent Libraries
&lt;/h3&gt;

&lt;p&gt;A category of libraries — including &lt;a href="https://www.e-iceblue.com/Introduce/excel-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.XLS&lt;/a&gt; and others — that implement Excel format support entirely in managed code, with no dependency on Office, COM, or Windows-specific APIs.&lt;/p&gt;

&lt;p&gt;In practice, these libraries usually offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A higher-level API than Open XML SDK&lt;/li&gt;
&lt;li&gt;Reliable behavior in server and containerized environments&lt;/li&gt;
&lt;li&gt;Support for a wider range of Excel features out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; ASP.NET applications that generate reports, process uploaded files, or run scheduled export jobs in production — especially where deployment simplicity matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth knowing:&lt;/strong&gt; some libraries in this category have per-developer or per-server licensing. Check the terms before committing to one for a commercial project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Office Required&lt;/th&gt;
&lt;th&gt;.xls Support&lt;/th&gt;
&lt;th&gt;API Level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dotnet/Open-XML-SDK" rel="noopener noreferrer"&gt;Open XML SDK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EPPlus&lt;/td&gt;
&lt;td&gt;Commercial (v5+)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/nissl-lab/npoi" rel="noopener noreferrer"&gt;NPOI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spire.XLS&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a id="4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Example: Reading Excel Files Without Interop
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete. Here's the same task implemented two ways — first with Interop, then without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; an ASP.NET application receives an uploaded Excel file, &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Create-Excel-File-in-C-VB.NET.html" rel="noopener noreferrer"&gt;reads the first sheet&lt;/a&gt;, and returns the value of cell A1.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interop Way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFormFile&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tempPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTempFileName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".xlsx"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="n"&gt;excel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Range&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;excel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Visible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Workbooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Worksheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excel&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the minimum viable version — and it's already fragile. Real production code usually ends up even more defensive than this. A single exception in the wrong place can prevent the cleanup code from running. The Excel process stays open. The temp file stays locked.&lt;/p&gt;

&lt;p&gt;In production, under concurrent load, this gets worse fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Without Interop
&lt;/h3&gt;

&lt;p&gt;Using Spire.XLS as an example — other libraries follow a similar pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Install-Package Spire.XLS&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"upload"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFormFile&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No file uploaded."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No COM objects. No manual cleanup. No temp files. No Excel process running in the background.&lt;/p&gt;

&lt;p&gt;The same result, in a fraction of the code — and it behaves predictably under concurrent load, inside a container, and on a Linux host.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnniatp7evkhb77ruq5dc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnniatp7evkhb77ruq5dc.webp" alt="Excel Interop vs modern library comparison" width="750" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pattern Holds for Writing Too
&lt;/h3&gt;

&lt;p&gt;Reading is the simple case. Here's &lt;a href="https://www.e-iceblue.com/Tutorials/NET/Spire.XLS-for-.NET/Program-Guide/Document-Operation/generate-excel-file-csharp.html" rel="noopener noreferrer"&gt;generating an Excel report&lt;/a&gt; and returning it as a download:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;DownloadReport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A1"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"B1"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Revenue"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"A2"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Widget A"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"B2"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;NumberValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;84500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version2016&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"report.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No installed Office. No cleanup ritual. Just code that does what it says.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters Beyond the Code
&lt;/h3&gt;

&lt;p&gt;The difference isn't just syntax. It's operational:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Excel process&lt;/strong&gt; means nothing to leak, nothing to orphan, nothing to kill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Office dependency&lt;/strong&gt; means the same code runs in dev, CI, staging, and production without special setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream-based API&lt;/strong&gt; means you can process uploaded files directly without writing to disk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other libraries in the same category work on the same principles. The specific API differs, but the underlying advantage is the same: you're working with a file format, not remote-controlling a desktop application.&lt;/p&gt;

&lt;p&gt;&lt;a id="5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When Interop Still Makes Sense
&lt;/h2&gt;

&lt;p&gt;It's worth being honest about this: Interop isn't always the wrong choice.&lt;/p&gt;

&lt;p&gt;If you're building an internal desktop tool — one that runs on a machine where Office is already installed, used interactively by a single person, and never deployed to a server — Interop can be a reasonable option. You're using Excel as Excel was designed to be used.&lt;/p&gt;

&lt;p&gt;Similarly, if your use case genuinely requires Excel-specific behavior that no file-format library supports — certain macro interactions, OLE embedding, or very specific rendering fidelity — Interop may be the only practical path.&lt;/p&gt;

&lt;p&gt;The problem isn't the technology itself. It's the mismatch between what Interop was designed for and where it gets used.&lt;/p&gt;

&lt;p&gt;For ASP.NET applications, background services, APIs, and anything that runs on a server or in a container, the mismatch is fundamental. The operational costs — fragile deployments, memory leaks, concurrency issues, licensing overhead — consistently outweigh the familiarity of the API.&lt;/p&gt;

&lt;p&gt;&lt;a id="6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. How to Migrate Away from Interop
&lt;/h2&gt;

&lt;p&gt;If Interop is already embedded in your codebase, a full rewrite isn't necessary. Most teams migrate incrementally — and that works well in practice.&lt;/p&gt;

&lt;p&gt;A reasonable approach looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify Interop-heavy workflows&lt;/strong&gt; — search for patterns like &lt;code&gt;using Excel = Microsoft.Office.Interop.Excel&lt;/code&gt; or &lt;code&gt;new Excel.Application()&lt;/code&gt;. These are reliable indicators of where Office automation is hiding in the codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrap existing logic behind interfaces&lt;/strong&gt; — don't rewrite Interop calls in place. Encapsulate them behind a service boundary first, so the replacement becomes a contained swap rather than a scattered refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace operations incrementally&lt;/strong&gt; — start with the highest-risk areas: anything in the ASP.NET request pipeline. Desktop utilities and internal tools can wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate file compatibility carefully&lt;/strong&gt; — different libraries handle edge cases differently. Run your existing test files through the new implementation before switching over in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load test under production-like conditions&lt;/strong&gt; — concurrency issues that were masked by Interop's serialized behavior can surface during migration. Test early.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zwp4tgngdw9yd4augzh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0zwp4tgngdw9yd4augzh.webp" alt="5-step migration process for Interop systems" width="750" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In practice, most Interop-heavy systems only use Excel for a handful of common tasks — reading uploads, generating reports, exporting spreadsheets — all of which modern libraries already handle well without launching Excel itself.&lt;/p&gt;

&lt;p&gt;&lt;a id="7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Excel Interop survives in production codebases for one reason: it works locally, and by the time the problems surface, it's already deeply embedded.&lt;/p&gt;

&lt;p&gt;But the issues covered in this post aren't bad luck or misconfiguration. Orphaned processes, hanging requests, concurrency failures, deployment blockers — these are the predictable consequences of running a desktop application in a server environment. They don't go away with more careful coding. They go away when you stop using the wrong tool for the job.&lt;/p&gt;

&lt;p&gt;The .NET ecosystem has mature, server-safe alternatives for working with Excel files. They're easier to deploy, easier to reason about, and they don't require a production runbook entry that says "kill orphaned Excel processes."&lt;/p&gt;

&lt;p&gt;If you're starting fresh, the choice is straightforward. If you're maintaining existing code, the migration path is incremental — but the direction is worth committing to.&lt;/p&gt;

&lt;p&gt;The spreadsheet may still be Excel. Your architecture doesn't have to be.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>productivity</category>
      <category>aspdotnet</category>
    </item>
    <item>
      <title>How to Merge PDFs in .NET Without Microsoft Office (Avoid Common Issues)</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Wed, 06 May 2026 09:57:18 +0000</pubDate>
      <link>https://dev.to/jenll/how-to-merge-pdfs-in-net-without-microsoft-office-avoid-common-issues-3n96</link>
      <guid>https://dev.to/jenll/how-to-merge-pdfs-in-net-without-microsoft-office-avoid-common-issues-3n96</guid>
      <description>&lt;p&gt;Merging PDFs sounds simple — until you try to do it in a real-world .NET application running on a server.&lt;/p&gt;

&lt;p&gt;Without a local desktop environment, approaches that rely on Microsoft Office quickly fall apart: licensing restrictions, deployment headaches, and poor performance under load make it a poor fit for server-side .NET applications.&lt;/p&gt;

&lt;p&gt;That's usually the point where developers start looking for alternatives.&lt;/p&gt;

&lt;p&gt;The good news is you don't need Office at all. Several dedicated .NET libraries handle PDF merging cleanly, without any Office dependency — and they give you far more control over the output.&lt;/p&gt;

&lt;p&gt;In this guide, we'll walk through the practical side of merging PDFs in .NET: a working setup, the issues developers most commonly run into, and how to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Clean Way to Merge PDFs in .NET
&lt;/h2&gt;

&lt;p&gt;When merging PDFs in a .NET application—especially on the server—the biggest challenge isn't writing the code itself. It's making sure the solution is reliable, scalable, and doesn't depend on external software like Microsoft Office.&lt;/p&gt;

&lt;p&gt;A more robust approach is to use a dedicated .NET PDF library that works independently of Office. This avoids common deployment issues and makes your solution suitable for environments like ASP.NET Core, Docker containers, or cloud services.&lt;/p&gt;

&lt;p&gt;In this article, we'll use &lt;a href="https://www.e-iceblue.com/Introduce/pdf-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.PDF for .NET&lt;/a&gt; as an example to demonstrate the process. The main reasons are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It works completely without Microsoft Office&lt;/li&gt;
&lt;li&gt;The API is straightforward and easy to integrate&lt;/li&gt;
&lt;li&gt;It handles common PDF merging scenarios (including large files and complex documents)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, the approach shown here isn't limited to a single library. The same concepts apply to &lt;strong&gt;most modern .NET PDF libraries&lt;/strong&gt;—you just need one that is stable, server-friendly, and doesn't introduce licensing or deployment complications.&lt;/p&gt;

&lt;p&gt;In the next section, we'll walk through the simplest way to merge PDFs in C# with a minimal, working example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Spire.PDF
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install the library
&lt;/h3&gt;

&lt;p&gt;Install Spire.PDF via &lt;a href="https://www.nuget.org/packages/Spire.pdf" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt; with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Spire.PDF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or search for &lt;code&gt;Spire.PDF&lt;/code&gt; in the Visual Studio NuGet Package Manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic PDF Merging - C# Sample
&lt;/h3&gt;

&lt;p&gt;Here's the simplest way to &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/Merge-PDF-Files-with-New-Method-in-C.html" rel="noopener noreferrer"&gt;merge PDFs in C#&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;MergePDFs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Specify the PDF files to be merged&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"sample0.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sample1.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sample2.pdf"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="c1"&gt;// Merge PDF files &lt;/span&gt;
            &lt;span class="n"&gt;PdfDocumentBase&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MergeFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Save the result file&lt;/span&gt;
            &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MergePDF.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp1c0ldzbtbt1fu2jh75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp1c0ldzbtbt1fu2jh75.png" alt="Merge PDF in Csharp" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PdfDocument.MergeFiles()&lt;/code&gt; method to merge multiple PDFs into a single file. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pdf.Save&lt;/code&gt; writes the merged document to disk in PDF format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Issues &amp;amp; Solutions
&lt;/h2&gt;

&lt;p&gt;Even with a straightforward API, PDF merging can go wrong in a few predictable ways. This section covers the most common ones — starting with the issues that tend to cause the most confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 1: Corrupted Output File
&lt;/h3&gt;

&lt;p&gt;The merged file is generated, but when you try to open it, your PDF reader throws an error — or the file size looks suspiciously small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this happen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common cause is that &lt;code&gt;SaveToFile()&lt;/code&gt; or &lt;code&gt;Close()&lt;/code&gt; was never called — or was called in the wrong order. If the program exits or throws an exception before the file is properly finalized, the output will be incomplete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to fix it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Always release resources after use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;sourceDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Tip: Use &lt;code&gt;using&lt;/code&gt; statements where possible to ensure proper disposal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 2: Missing Fonts or Layout Issues
&lt;/h3&gt;

&lt;p&gt;Text in the merged PDF appears garbled, substituted with a fallback font, or missing entirely — even though the original files looked fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this happen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PDFs may reference fonts without embedding them. When those fonts aren’t available in the runtime environment, the merged result can display incorrectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to fix it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most reliable solution is to ensure fonts are embedded in the source PDFs before merging. This guarantees consistent rendering across environments.&lt;/p&gt;

&lt;p&gt;If you don’t control the source files, process and merge them using a consistent environment, and avoid relying on system-installed fonts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;mergedDocument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 In production, always test with real-world documents—not just simple PDFs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 3: Out of Memory When Merging Large Files
&lt;/h3&gt;

&lt;p&gt;The merge works fine for small files, but throws a &lt;code&gt;System.OutOfMemoryException&lt;/code&gt; — or causes noticeable memory pressure — when the input files are large or numerous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this happen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, loading a &lt;code&gt;PdfDocument&lt;/code&gt; reads the entire file into memory. If you're merging ten 50MB PDFs, you're potentially holding 500MB in memory at once — before the output document is even written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to fix it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Process files one at a time and release each one before loading the next. Avoid holding all source documents open simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileStream&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileAccess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Going further&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For very large batches, consider merging in chunks — combine files in groups of 5–10, write each chunk to a temp file, then do a final merge of the temp files. This keeps peak memory usage bounded regardless of input size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Common Issues
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Form fields and annotations disappear after merging&lt;/strong&gt;&lt;br&gt;
By default, merging flattens interactive elements. If you need to preserve form fields, set &lt;code&gt;merged.FileInfo.IncrementalUpdate = true&lt;/code&gt; before appending pages, or explicitly copy the &lt;a href="https://pdfa.org/resource/acroform/" rel="noopener noreferrer"&gt;AcroForm&lt;/a&gt; structure from each source document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password-protected PDFs fail to merge&lt;/strong&gt;&lt;br&gt;
Spire.PDF will throw an exception if it encounters an encrypted file without credentials. Load protected files with the password explicitly: &lt;code&gt;new PdfDocument(file, "yourpassword")&lt;/code&gt; — then append as normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pages come out in the wrong order&lt;/strong&gt;&lt;br&gt;
This is almost always a file list ordering issue, not a library bug. Sort your input array explicitly before merging rather than relying on filesystem or directory enumeration order, which varies by OS and environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Advanced Scenarios
&lt;/h2&gt;

&lt;p&gt;Once basic merging is working reliably, there are a few common extensions worth knowing about. You don’t need all of these for basic use—pick what fits your scenario.&lt;/p&gt;

&lt;p&gt;Here's a minimal example you can copy and run:&lt;/p&gt;
&lt;h3&gt;
  
  
  Merging Specific Pages Only
&lt;/h3&gt;

&lt;p&gt;Sometimes you don't need the entire document — just &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/Document-Operation/Merge-Selected-Pages-from-Multiple-PDF-Files-into-One-in-C-VB.NET.html" rel="noopener noreferrer"&gt;merge a range of pages from each source file&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load source PDFs&lt;/span&gt;
&lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pdfs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sample0.pdf"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sample1.pdf"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sample2.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create merged document&lt;/span&gt;
&lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;newPDF&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Insert selected pages&lt;/span&gt;
&lt;span class="n"&gt;newPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertPageRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Pages 1-2 from first PDF&lt;/span&gt;
&lt;span class="n"&gt;newPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// First page from second PDF&lt;/span&gt;
&lt;span class="n"&gt;newPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdfs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// Second page from third PDF&lt;/span&gt;

&lt;span class="c1"&gt;// Save result&lt;/span&gt;
&lt;span class="n"&gt;newPDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SelectivePageMerging.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4on6spgnw51j0qnpjkk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4on6spgnw51j0qnpjkk5.png" alt="Merging Specific Pages Only in Csharp" width="800" height="706"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is useful when you're pulling specific sections from multiple reports into a single summary document.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a Watermark During Merge
&lt;/h3&gt;

&lt;p&gt;If you need to stamp each page with a watermark — a company name, "CONFIDENTIAL", a draft label — the merge step is a natural place to do it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf.Graphics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Drawing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Merge&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MergeFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"doc1.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"doc2.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"doc3.pdf"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tempPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTempFileName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Add watermark&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfTrueTypeFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;50f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FontStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetTransparency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TranslateTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RotateTransform&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;45&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CONFIDENTIAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfBrushes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DarkGray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetTransparency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"merged_with_watermark.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns44h0aak4xdlgchdgg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fns44h0aak4xdlgchdgg9.png" alt="Adding a Watermark During Merge" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adjust the transparency, font size, and position to suit your layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Page Numbers After Merging
&lt;/h3&gt;

&lt;p&gt;Page numbers should reflect the final document, not the originals — so &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/PDF-Merge-%E2%80%93-Merge-PDF-Files-and-Add-Page-Number-with-C-VB.NET.html" rel="noopener noreferrer"&gt;add page numbers after the merge&lt;/a&gt; is complete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf.Graphics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Merge&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MergeFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;@"D:\doc1.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;@"D:\doc2.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;@"D:\doc3.pdf"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTempFileName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Add numbers&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfTrueTypeFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Page &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MeasureString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;50f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfBrushes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Height&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;50f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"D:\numbered_merged.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftz5xqp1vp7z5wofe1o0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftz5xqp1vp7z5wofe1o0a.png" alt="Adding Page Numbers After Merging" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing Merge as an ASP.NET Core API Endpoint
&lt;/h3&gt;

&lt;p&gt;If you're building a service that accepts PDF uploads and returns a merged file, here's a &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis" rel="noopener noreferrer"&gt;minimal endpoint structure&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/merge-stream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"At least two files are required."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="c1"&gt;// Load PDF from stream&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tempDoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Insert all pages&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tempDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempDoc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"merged.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error merging PDFs: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to keep in mind for production use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate file types before processing&lt;/li&gt;
&lt;li&gt;Set a reasonable size limit per upload&lt;/li&gt;
&lt;li&gt;Consider running the merge in a background job for large files rather than blocking the request thread&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Async Batch Processing for Large Volumes
&lt;/h3&gt;

&lt;p&gt;For pipelines that process many files at once, avoid blocking threads on I/O. Here's a simple async wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;MergePdfsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Since Spire.PDF's MergeFiles doesn't support async I/O,&lt;/span&gt;
    &lt;span class="c1"&gt;// you can omit the async wrapper in practice, or just add a caching layer&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filesArray&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Use the officially recommended MergeFiles method&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mergedDocument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MergeFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filesArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;mergedDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For high-throughput scenarios, pair this with a queue (e.g., Azure Service Bus or a simple &lt;code&gt;[Channel&amp;lt;T&amp;gt;](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels)&lt;/code&gt;) to process merge jobs one at a time rather than in parallel — PDF merging is memory-intensive enough that concurrency can backfire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative Libraries
&lt;/h2&gt;

&lt;p&gt;Spire.PDF works well for the scenarios covered in this guide, but it's not the only option. Depending on your project's requirements — licensing, budget, or specific feature needs — one of these alternatives may be a better fit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Highlights&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spire.PDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Commercial (free tier available)&lt;/td&gt;
&lt;td&gt;Clean API, good documentation, server-friendly&lt;/td&gt;
&lt;td&gt;General-purpose merging, teams that want a supported library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;iText 7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AGPL / Commercial&lt;/td&gt;
&lt;td&gt;Industry standard, highly capable, large community&lt;/td&gt;
&lt;td&gt;Complex PDF manipulation, enterprise projects with commercial license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PDFsharp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Lightweight, no cost, simple API&lt;/td&gt;
&lt;td&gt;Open-source projects, basic merging without advanced requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PdfPig&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache 2.0&lt;/td&gt;
&lt;td&gt;Fully open-source, actively maintained&lt;/td&gt;
&lt;td&gt;Reading and extracting PDF content, lightweight merging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aspose.PDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Feature-rich, excellent format support&lt;/td&gt;
&lt;td&gt;Teams that need broad format coverage and vendor support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;👉 Quick recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Spire.PDF&lt;/strong&gt; for ease of use and quick integration.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://itextpdf.com" rel="noopener noreferrer"&gt;iText 7&lt;/a&gt;&lt;/strong&gt; if licensing is not an issue.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/empira/PDFsharp" rel="noopener noreferrer"&gt;PDFsharp&lt;/a&gt;&lt;/strong&gt; for simple open-source scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these libraries can handle PDF merging—the right choice depends on your licensing constraints and how complex your use case is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Merging PDFs in .NET is straightforward—until real-world constraints come into play.&lt;/p&gt;

&lt;p&gt;By using a library that doesn't depend on Microsoft Office and understanding the common pitfalls, you can build a solution that's both reliable and production-ready.&lt;/p&gt;

&lt;p&gt;Once you have the basics in place, you can extend the same approach to more advanced scenarios like batching, APIs, or document workflows.&lt;/p&gt;

&lt;p&gt;Start with a simple merge, validate it with real-world files, and build from there.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>productivity</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Best .NET PDF Libraries in 2026: Free vs Paid (What Actually Works)</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 27 Apr 2026 03:38:54 +0000</pubDate>
      <link>https://dev.to/jenll/best-net-pdf-libraries-in-2026-free-vs-paid-what-actually-works-5127</link>
      <guid>https://dev.to/jenll/best-net-pdf-libraries-in-2026-free-vs-paid-what-actually-works-5127</guid>
      <description>&lt;p&gt;It usually starts with something simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We just need to export a PDF."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds straightforward. Until it isn't.&lt;/p&gt;

&lt;p&gt;At first, everything seems fine. You pick a library, write a few lines of code, and get a PDF output. Done.&lt;/p&gt;

&lt;p&gt;Then the real problems begin.&lt;/p&gt;

&lt;p&gt;Not in development — but the moment you deploy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The layout looks completely different from your HTML.&lt;/li&gt;
&lt;li&gt;Fonts are missing or rendering incorrectly.&lt;/li&gt;
&lt;li&gt;It works on your machine… but fails in Docker or Linux.&lt;/li&gt;
&lt;li&gt;Performance drops when processing larger files.&lt;/li&gt;
&lt;li&gt;And suddenly, licensing becomes a concern you didn't plan for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What felt like a quick task turns into a series of unexpected trade-offs.&lt;/p&gt;

&lt;p&gt;The truth is, choosing a PDF library in .NET isn't about features — it's about what actually works in production.&lt;/p&gt;

&lt;p&gt;In this article, we'll take a practical look at some of the most popular .NET PDF libraries in 2026, compare free and paid options, and — more importantly — help you figure out which one actually fits your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem — Why Choosing a PDF Library Is Harder Than It Looks
&lt;/h2&gt;

&lt;p&gt;On the surface, most PDF libraries look similar: create documents, add text, insert images, maybe even support HTML rendering.&lt;/p&gt;

&lt;p&gt;But in practice, the differences start to matter very quickly.&lt;/p&gt;

&lt;p&gt;The challenge isn't generating a PDF — it's generating the &lt;em&gt;right&lt;/em&gt; PDF, under real-world conditions.&lt;/p&gt;

&lt;p&gt;Here are a few trade-offs developers often run into:&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Complexity vs. Simplicity
&lt;/h3&gt;

&lt;p&gt;Some libraries give you full control over every element — layout, positioning, rendering.&lt;br&gt;
That's powerful, but it also means more code, more setup, and a steeper learning curve.&lt;/p&gt;

&lt;p&gt;Others are much easier to use, but become limiting once your requirements grow beyond simple documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML Rendering vs. Manual Layout
&lt;/h3&gt;

&lt;p&gt;If your PDFs are based on existing HTML (invoices, reports, web pages), HTML-to-PDF support becomes critical.&lt;/p&gt;

&lt;p&gt;But not all libraries handle this well.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some don't support HTML at all.&lt;/li&gt;
&lt;li&gt;Some only support partial or inconsistent rendering.&lt;/li&gt;
&lt;li&gt;Others rely on embedded browsers, which introduces performance and deployment considerations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Local Development vs. Production Environments
&lt;/h3&gt;

&lt;p&gt;This is where a lot of library choices fall apart in practice. &lt;/p&gt;

&lt;p&gt;Common issues include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing system dependencies in &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/docker/introduction" rel="noopener noreferrer"&gt;Linux or Docker&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Font rendering differences.&lt;/li&gt;
&lt;li&gt;Headless environment limitations.&lt;/li&gt;
&lt;li&gt;Unexpected crashes under load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3mgrzv9l4i0z8d9nhw0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3mgrzv9l4i0z8d9nhw0.webp" alt="Local Development vs. Server Environments" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Free vs. Paid — More Than Just Cost
&lt;/h3&gt;

&lt;p&gt;At first glance, the decision seems simple: use a free library if possible.&lt;/p&gt;

&lt;p&gt;But "free" often comes with trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited features.&lt;/li&gt;
&lt;li&gt;More time spent on implementation.&lt;/li&gt;
&lt;li&gt;Licensing restrictions (especially for commercial use).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the other hand, paid libraries usually offer more complete solutions — but introduce cost and vendor dependency.&lt;/p&gt;

&lt;p&gt;All of this leads to one important conclusion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no universally "best" PDF library in .NET — only the one that best fits your specific use case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Free vs Paid — What's the Actual Difference?
&lt;/h2&gt;

&lt;p&gt;At some point, almost every developer asks the same question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can I just use a free library?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is: &lt;strong&gt;yes — but it depends on what you need.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The difference between free and paid PDF libraries isn't just about cost. It's about &lt;strong&gt;where you spend your effort&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟢 Free / Open-Source Libraries
&lt;/h3&gt;

&lt;p&gt;Free libraries are often the first choice — and for good reason.&lt;/p&gt;

&lt;p&gt;They're accessible, flexible, and backed by active communities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they do well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No upfront cost.&lt;/li&gt;
&lt;li&gt;Full control over implementation.&lt;/li&gt;
&lt;li&gt;Suitable for simple or well-defined use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where they get challenging:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited or no HTML-to-PDF support.&lt;/li&gt;
&lt;li&gt;More manual work (layout, pagination, styling).&lt;/li&gt;
&lt;li&gt;Inconsistent behavior across environments.&lt;/li&gt;
&lt;li&gt;Licensing constraints (e.g., AGPL in some cases).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, they work best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're generating structured documents (reports, invoices).&lt;/li&gt;
&lt;li&gt;You don't rely heavily on HTML rendering.&lt;/li&gt;
&lt;li&gt;You're okay investing more development time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔵 Paid / Commercial Libraries
&lt;/h3&gt;

&lt;p&gt;Commercial libraries take a different approach: they aim to &lt;strong&gt;solve the entire problem out of the box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of building everything yourself, you get a more complete toolkit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they do well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliable HTML-to-PDF rendering.&lt;/li&gt;
&lt;li&gt;Better handling of fonts, layouts, and pagination.&lt;/li&gt;
&lt;li&gt;Consistent behavior across environments (Windows, Linux, Docker).&lt;/li&gt;
&lt;li&gt;Dedicated support and documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Licensing cost.&lt;/li&gt;
&lt;li&gt;Vendor lock-in.&lt;/li&gt;
&lt;li&gt;Sometimes heavier dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're typically a better fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need production-ready stability.&lt;/li&gt;
&lt;li&gt;You're building backend services or automation pipelines.&lt;/li&gt;
&lt;li&gt;Time-to-market matters more than implementation flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Real Trade-Off
&lt;/h3&gt;

&lt;p&gt;This is where most comparisons miss the point.&lt;/p&gt;

&lt;p&gt;It's not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Free vs Paid&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's actually:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Time vs Money&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In most real-world projects, you don't really choose a library — you choose your trade-offs.&lt;/p&gt;

&lt;p&gt;Free libraries often save money upfront, but require more engineering effort.&lt;/p&gt;

&lt;p&gt;Paid libraries cost more initially, but can significantly reduce development time and risk.&lt;/p&gt;

&lt;p&gt;If you're building something quick or highly customized, a free library might be enough.&lt;/p&gt;

&lt;p&gt;But if you're building something that needs to &lt;strong&gt;run reliably in production&lt;/strong&gt;, the decision becomes less about cost — and more about stability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqevsboalyybu2fuxjkje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqevsboalyybu2fuxjkje.png" alt="Free vs Paid — What's the Actual Difference" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Library Breakdown — What Actually Works
&lt;/h2&gt;

&lt;p&gt;Enough context. Let's get into the libraries themselves.&lt;/p&gt;

&lt;p&gt;Before we dive into specific libraries, one important note: what follows isn't a rehash of each library's feature page. It's an honest assessment of where each one shines, where it struggles, and what kind of project it's actually suited for.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟢 Open-Source Options
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;QuestPDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.questpdf.com/" rel="noopener noreferrer"&gt;QuestPDF&lt;/a&gt; is probably the most exciting thing to happen to .NET PDF generation in recent years. It takes a code-first, Fluent API approach — you describe your document structure in C#, and it renders it precisely and consistently.&lt;/p&gt;

&lt;p&gt;The results are clean, the API is genuinely pleasant to work with, and it handles things like pagination and repeating headers without the usual pain. For structured documents — invoices, financial reports, delivery notes — it's hard to beat &lt;a href="https://github.com/QuestPDF/QuestPDF" rel="noopener noreferrer"&gt;at any price&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The limitation is equally clear: &lt;strong&gt;QuestPDF doesn't do HTML rendering.&lt;/strong&gt; If your documents are driven by Razor templates or existing HTML/CSS, this isn't your tool. You'll be rebuilding your layout in C# from scratch, which is a significant investment depending on complexity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Beautiful Fluent API, precise layout control, great for structured docs.&lt;/li&gt;
&lt;li&gt;👎 No HTML support, requires code-based layout for everything.&lt;/li&gt;
&lt;li&gt;✅ Best for: Invoices, reports, any document with a predictable structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PDFsharp&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pdfsharp.com/" rel="noopener noreferrer"&gt;PDFsharp&lt;/a&gt; is the "old reliable" of the .NET open-source PDF world. It's been around for years, it's lightweight, and it does what it says.&lt;/p&gt;

&lt;p&gt;That said, it's showing its age. The API is lower-level than modern alternatives, documentation is sparse, and complex layout work requires a lot of manual effort. It's not a library you'd choose for a serious production system in 2026 — but for simple one-off PDFs or quick prototypes, it gets the job done without ceremony.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Lightweight, no frills, easy to get started.&lt;/li&gt;
&lt;li&gt;👎 Low-level API, limited features, not actively developed.&lt;/li&gt;
&lt;li&gt;✅ Best for: Simple PDF generation, quick prototypes, low-complexity needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;iText 7 (AGPL)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itextpdf.com/" rel="noopener noreferrer"&gt;iText&lt;/a&gt; is the most powerful open-source PDF library in the .NET ecosystem — and also the most misunderstood.&lt;/p&gt;

&lt;p&gt;The functionality is exceptional. Fine-grained control over PDF structure, solid form handling, digital signatures, PDF/A compliance — if you need to do something with a PDF, iText can probably do it. The API has a learning curve, but it rewards the investment.&lt;/p&gt;

&lt;p&gt;The real issue is the license. iText 7 is released under &lt;a href="https://itextpdf.com/how-buy/AGPLv3-license" rel="noopener noreferrer"&gt;AGPL&lt;/a&gt;, which requires any software using it to also be open-sourced under AGPL. For commercial applications, that's almost certainly not acceptable — which means you'd need their commercial license, at which point it's no longer a "free" option.&lt;/p&gt;

&lt;p&gt;Use it for open-source projects freely. For everything else, price out the commercial license before you build anything on top of it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Extremely powerful, precise PDF control, handles complex requirements.&lt;/li&gt;
&lt;li&gt;👎 AGPL license is a serious constraint for commercial projects.&lt;/li&gt;
&lt;li&gt;✅ Best for: Open-source projects, or commercial projects with budget for the paid license.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔵 Commercial Options
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IronPDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your primary use case is converting HTML to PDF, &lt;a href="https://ironpdf.com/" rel="noopener noreferrer"&gt;IronPDF&lt;/a&gt; is hard to ignore. It's &lt;a href="https://ironpdf.com/features/" rel="noopener noreferrer"&gt;built on Chromium&lt;/a&gt;, which means what you get out is very close to what a real browser would render — CSS, web fonts, JavaScript-rendered content, all of it.&lt;/p&gt;

&lt;p&gt;For teams that maintain their document templates as HTML and want pixel-accurate output, that's a genuinely compelling proposition.&lt;/p&gt;

&lt;p&gt;The tradeoff is resource usage. Chromium is not a lightweight dependency. Cold start times are noticeable, memory footprint is higher than other options, and in high-throughput scenarios you'll need to think carefully about how you manage instances. It's not a dealbreaker, but it's something to test against your actual load before committing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Best-in-class HTML rendering, near-perfect browser fidelity.&lt;/li&gt;
&lt;li&gt;👎 High resource usage, slower cold starts, heavier infrastructure footprint.&lt;/li&gt;
&lt;li&gt;✅ Best for: HTML-heavy documents, web page to PDF conversion, template-driven output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Aspose.PDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://products.aspose.com/pdf/net/" rel="noopener noreferrer"&gt;Aspose&lt;/a&gt; is the enterprise choice — and it earns that label in both directions.&lt;/p&gt;

&lt;p&gt;The feature set is genuinely comprehensive: PDF generation, editing, conversion to and from dozens of formats, OCR, digital signatures, PDF/A and PDF/UA compliance, redaction. If you have a complex document processing requirement, Aspose almost certainly supports it. The library is mature, well-maintained, and the documentation is thorough.&lt;/p&gt;

&lt;p&gt;The cost reflects that. Aspose is among the most expensive options in this space, and the API can feel heavy for simpler use cases. If you're building a straightforward report generator, it's more library than you need.&lt;/p&gt;

&lt;p&gt;But for large-scale systems where document processing is a core part of the product — legal platforms, financial systems, document management pipelines — the investment tends to pay off. You're not going to hit a wall with Aspose.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Most complete feature set, enterprise-grade reliability, extensive format support.&lt;/li&gt;
&lt;li&gt;👎 Highest price point, API complexity, more than most projects need.&lt;/li&gt;
&lt;li&gt;✅ Best for: Enterprise systems, complex document workflows, organizations with serious compliance requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Spire.PDF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.e-iceblue.com/Introduce/pdf-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.PDF&lt;/a&gt; sits in an interesting position: it's not the flashiest option, and it doesn't get talked about as much as IronPDF or Aspose — but in practice, it quietly handles a lot of real-world scenarios well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/Convert-HTML-to-PDF-Customize-HTML-to-PDF-Conversion-by-Yourself.html" rel="noopener noreferrer"&gt;HTML to PDF conversion&lt;/a&gt; is stable and doesn't require a full browser engine. The API is straightforward without being simplistic. It runs cleanly on Linux and inside Docker containers without needing special configuration — which is exactly where some browser-based solutions start to show friction, usually at the worst possible time: during deployment.&lt;/p&gt;

&lt;p&gt;It won't win any design awards for its API, but for backend automation — generating documents server-side, processing PDFs in bulk, integrating into pipelines — it's a practical, reliable choice that won't surprise you in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👍 Stable HTML to PDF, clean Linux/Docker support, straightforward API.&lt;/li&gt;
&lt;li&gt;👎 Less community presence, not as well known as larger alternatives.&lt;/li&gt;
&lt;li&gt;✅ Best for: Server-side automation, backend document generation, Docker-based deployments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc50zxnv64wlelccsqo4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc50zxnv64wlelccsqo4g.png" alt="Library Breakdown — What Actually Works" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;p&gt;If you just want the quick answer, here's how they compare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;HTML to PDF&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Ease&lt;/th&gt;
&lt;th&gt;Performance&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;QuestPDF&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Structured reports, invoices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFsharp&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Simple PDF generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iText&lt;/td&gt;
&lt;td&gt;⚠️ Partial&lt;/td&gt;
&lt;td&gt;AGPL / Paid&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Advanced PDF control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;td&gt;HTML to PDF (web content)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aspose&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Enterprise-level solutions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spire.PDF&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;Automation, backend services&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A few notes on the table:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML to PDF&lt;/strong&gt; — ⚠️ on iText means partial support exists but isn't a core strength. Don't build an HTML-driven workflow around it without testing first. ⚠️ on IronPDF for Linux/Docker means it works, but requires more setup than other options — Chromium dependencies need to be explicitly handled in your container.&lt;/p&gt;

&lt;h2&gt;
  
  
  So… Which One Should You Choose?
&lt;/h2&gt;

&lt;p&gt;If you don’t want to overthink it, here's the short version:&lt;/p&gt;

&lt;h3&gt;
  
  
  👉 You need to generate structured documents — invoices, reports, statements
&lt;/h3&gt;

&lt;p&gt;Go with &lt;strong&gt;QuestPDF&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean API&lt;/li&gt;
&lt;li&gt;Great layout control&lt;/li&gt;
&lt;li&gt;No need for HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👉 Your documents are HTML-based and you need accurate rendering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go with &lt;strong&gt;IronPDF&lt;/strong&gt; if output fidelity is the priority and you can absorb the resource overhead.&lt;/li&gt;
&lt;li&gt;If you're running in a lightweight backend environment and need something more predictable, &lt;strong&gt;Spire.PDF&lt;/strong&gt; is worth evaluating — the rendering isn't browser-identical, but it's consistent and far less demanding on infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👉 You need complete control over PDF internals — signatures, compliance, low-level manipulation
&lt;/h3&gt;

&lt;p&gt;Go with &lt;strong&gt;iText&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Powerful but complex&lt;/li&gt;
&lt;li&gt;Best for advanced scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👉 You're building backend automation — bulk generation, scheduled jobs, document pipelines
&lt;/h3&gt;

&lt;p&gt;Go with a &lt;strong&gt;commercial library (Spire, IronPDF, Aspose)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More stable across environments&lt;/li&gt;
&lt;li&gt;Less time spent debugging edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👉 You just need something simple and fast
&lt;/h3&gt;

&lt;p&gt;Go with &lt;strong&gt;PDFsharp&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Easy to set up&lt;/li&gt;
&lt;li&gt;Good enough for basic tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Advice — What the Docs Won't Tell You
&lt;/h2&gt;

&lt;p&gt;By this point, the differences between libraries should be clearer. But in practice, a few factors matter more than anything listed in a feature comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Don't choose based on features alone
&lt;/h3&gt;

&lt;p&gt;Most libraries &lt;em&gt;claim&lt;/em&gt; to support similar things.&lt;br&gt;
The real difference shows up when you run them in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test in your actual environment early
&lt;/h3&gt;

&lt;p&gt;Especially if you're deploying to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Cloud environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What works locally may not work there.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pay attention to licensing
&lt;/h3&gt;

&lt;p&gt;This is often overlooked - until it becomes a problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some "free" libraries have strict licenses (like AGPL)&lt;/li&gt;
&lt;li&gt;Commercial use may require purchasing a license&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Start with a small proof of concept (POC)
&lt;/h3&gt;

&lt;p&gt;Before committing to a library, try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering a real document&lt;/li&gt;
&lt;li&gt;Testing performance&lt;/li&gt;
&lt;li&gt;Checking output consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few hours of testing can save days of refactoring later.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Think long-term, not just quick wins
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The wrong library usually doesn't fail immediately — it fails when you scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvls96nyfi9dmwntynlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvls96nyfi9dmwntynlp.png" alt="Real-World Advice — What the Docs Won't Tell You" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;There's no perfect PDF library for .NET. There's only the right one for what you're actually building.&lt;/p&gt;

&lt;p&gt;If this article has a single takeaway, it's this: &lt;strong&gt;start with your constraints, not the feature list.&lt;/strong&gt; Your deployment environment, your license requirements, your document structure, your team's capacity — those filters will do most of the work for you. By the time you've answered those questions honestly, the shortlist usually writes itself.&lt;/p&gt;

&lt;p&gt;If you're working on document generation or automation in .NET, taking the time to choose the right library early can save you a lot of trouble later.&lt;/p&gt;

&lt;p&gt;And in the end, the best PDF library isn't the most powerful one — it's the one that keeps working when everything else starts to break.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>productivity</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Why Microsoft Office Interop Fails for PDF Generation in .NET (And What to Use Instead)</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Tue, 21 Apr 2026 01:59:09 +0000</pubDate>
      <link>https://dev.to/jenll/why-microsoft-office-interop-fails-for-pdf-generation-in-net-and-what-to-use-instead-5bc6</link>
      <guid>https://dev.to/jenll/why-microsoft-office-interop-fails-for-pdf-generation-in-net-and-what-to-use-instead-5bc6</guid>
      <description>&lt;p&gt;Microsoft Office Interop is still widely used for PDF generation in .NET because it feels like a quick win. It works on a local machine, requires minimal code, and produces accurate results by leveraging Microsoft Office itself.&lt;/p&gt;

&lt;p&gt;The problems start in production. Applications begin to hang, background processes like &lt;code&gt;WINWORD.EXE&lt;/code&gt; accumulate, and stability degrades under load—not because of application logic, but because Interop was built for desktop automation rather than server-side workloads.&lt;/p&gt;

&lt;p&gt;Built for desktop automation, Interop depends on a GUI environment and system states that don’t exist in modern backend architectures. This article explains why it fails—and what to use instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Microsoft Office Interop (And Why It's So Common)
&lt;/h2&gt;

&lt;p&gt;Microsoft Office Interop is a set of .NET assemblies that allow applications to automate Microsoft Office programs such as Word and Excel. In the context of PDF generation, it is often used to open a document and export it directly to PDF using the built-in capabilities of Office.&lt;/p&gt;

&lt;p&gt;Its popularity comes from a few practical advantages. First, it feels familiar—developers are effectively controlling tools they already know. Second, the API surface is relatively straightforward, making it easy to get a working solution with minimal code. And because the output is generated by Microsoft Office itself, the formatting accuracy is typically high.&lt;/p&gt;

&lt;p&gt;This combination makes Interop especially appealing for quick implementations or internal tools. However, the same characteristics that make it convenient during development can become limitations once the application is deployed to a production environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Interop Fails in Production
&lt;/h2&gt;

&lt;p&gt;The issues with Microsoft Office Interop are not incidental—they are a direct result of its design.&lt;/p&gt;

&lt;p&gt;The core problems fall into a few categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Fundamentally Not Designed for Server Environments
&lt;/h3&gt;

&lt;p&gt;Interop doesn't just use Office — it &lt;em&gt;is&lt;/em&gt; Office. Every API call drives a full desktop application running in the background. &lt;a href="https://support.microsoft.com/en-us/topic/considerations-for-server-side-automation-of-office-48bcfe93-8a89-47f1-0bce-017433ad79e2" rel="noopener noreferrer"&gt;Microsoft's own documentation explicitly states&lt;/a&gt; that Office is not designed for unattended server-side execution and is unsupported in that context.&lt;/p&gt;

&lt;p&gt;Server environments don't have desktop sessions, logged-in users, or displays. Interop assumes all three. When those assumptions break, dialog boxes appear with no one to dismiss them, file pickers block indefinitely, and processes stall waiting for input that never comes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requires Microsoft Office Installation (Hard Dependency)
&lt;/h3&gt;

&lt;p&gt;Every machine that runs your code needs a licensed Office installation — your web server, your build agent, your Docker container. In practice this means two problems: you can't containerize cleanly, and version inconsistencies between Office 2019 and Microsoft 365 mean the same document can render differently across environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stability Issues (COM Lifecycle Complexity)
&lt;/h3&gt;

&lt;p&gt;COM objects don't clean themselves up. Every &lt;code&gt;Document&lt;/code&gt; and &lt;code&gt;Application&lt;/code&gt; object needs explicit &lt;code&gt;Marshal.ReleaseComObject()&lt;/code&gt; calls. Miss one, and the Office process keeps running after your code exits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;finally&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForPendingFinalizers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even this pattern isn't bulletproof. Under certain error conditions the process still doesn't exit, and orphaned &lt;code&gt;WINWORD.EXE&lt;/code&gt; instances accumulate quietly until the server runs out of memory. Interop is also not thread-safe — in a multi-threaded web server, race conditions are a matter of when, not if.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poor Scalability (Process-Per-Request Model)
&lt;/h3&gt;

&lt;p&gt;Each conversion request starts a new Office instance. Under any real load, this means multiple full Office processes running simultaneously, each consuming hundreds of megabytes. Batch processing is no better — failures become silent and unpredictable once you're iterating over large file sets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Interop works fine for demos — but breaks under real production workloads.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What a Modern PDF Solution Should Provide
&lt;/h2&gt;

&lt;p&gt;A production-ready PDF solution should meet the expectations of modern backend applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Office dependency&lt;/strong&gt; — a NuGet package, nothing more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server environment support&lt;/strong&gt; — ASP.NET Core, Docker, Linux, no display required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability under load&lt;/strong&gt; — bounded memory, no process leaks, thread-safe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean .NET API&lt;/strong&gt; — idiomatic C#, no COM artifacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming and batch support&lt;/strong&gt; — &lt;code&gt;MemoryStream&lt;/code&gt; for web APIs, stable iteration for batch jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These requirements reflect how applications are built and deployed today—and highlight why Interop struggles in these scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landscape: Standalone .NET PDF Libraries
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://itextpdf.com/how-buy/legal/agpl-gnu-affero-general-public-license" rel="noopener noreferrer"&gt;iText 7&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Feature-complete, large community&lt;/td&gt;
&lt;td&gt;AGPL — commercial use requires paid license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PdfSharp + MigraDoc&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully open source&lt;/td&gt;
&lt;td&gt;Weak Word conversion support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QuestPDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Elegant fluent API for building documents&lt;/td&gt;
&lt;td&gt;Weak support for converting existing Word/Excel files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spire.Doc / Spire.PDF for .NET&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No Office dependency, reliable conversion, cross-platform&lt;/td&gt;
&lt;td&gt;Free tier has page limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Puppeteer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Flexible HTML→PDF&lt;/td&gt;
&lt;td&gt;Heavy runtime (browser dependency)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Different tools fit different scenarios. For generating PDFs from scratch, libraries like &lt;a href="https://www.questpdf.com" rel="noopener noreferrer"&gt;QuestPDF&lt;/a&gt; offer a clean developer experience. For HTML-based workflows, headless browsers provide flexibility.&lt;/p&gt;

&lt;p&gt;However, when the requirement is &lt;strong&gt;reliable server-side conversion of existing Word or Excel documents&lt;/strong&gt;, the priorities change: no Office dependency, consistent behavior across environments, and stable performance under load. This is where standalone libraries designed specifically for backend processing, like Spire.Doc and Spire.PDF for .NET, become the most practical choice. In practice, different libraries handle different parts of the workflow—for example, Spire.Doc for &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.Doc/Spire.Doc-Program-Guide/How-to-Convert-Word-to-PDF.html" rel="noopener noreferrer"&gt;Word-to-PDF conversion&lt;/a&gt; and Spire.PDF for &lt;a href="https://www.e-iceblue.com/Tutorials/NET/Spire.PDF-for-.NET/Program-Guide/Document-Operation/asp-net-create-pdf.html" rel="noopener noreferrer"&gt;creating or manipulating PDF documents&lt;/a&gt; directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhg1tvz5xu5hynvu1jnqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhg1tvz5xu5hynvu1jnqy.png" alt="Spire.PDF for .NET" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Examples: Interop vs a Modern Approach
&lt;/h2&gt;

&lt;p&gt;These limitations become obvious when you compare Interop with modern libraries in real-world scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario A: Creating a PDF Report from Scratch
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;While Interop isn't typically used for creating PDFs from scratch, this comparison highlights how different the programming models are.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Using Interop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Office&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Paragraphs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Quarterly Report"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WdSaveFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wdFormatPDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even for a simple document, you're launching a full Office instance, managing COM object lifecycles manually, and hoping nothing throws before the cleanup code runs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This approach is error-prone and difficult to maintain in backend code.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ✅ Using Spire.PDF
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;PdfPageBase&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;PdfTrueTypeFont&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfTrueTypeFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Arial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;14f&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;PdfBrush&lt;/span&gt; &lt;span class="n"&gt;brush&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfBrushes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Quarterly Report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Creating PDFs becomes a straightforward, in-process operation.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Scenario B: Converting Existing Documents to PDF
&lt;/h3&gt;

&lt;p&gt;This is where Interop causes the most production pain. The typical implementation looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ Using Interop&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Visible&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WdSaveFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wdFormatPDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReleaseComObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locally, this works. In production, it requires Office installed, runs single-threaded, leaks processes under error conditions, and can lock files in ways that require manual cleanup on the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Using Spire.Doc&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Office. No COM cleanup. No process management. The same three lines work identically on Windows, Linux, and inside a Docker container.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83p0ho44vb7rcit5zayi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83p0ho44vb7rcit5zayi.png" alt="C#: Convert Word to PDF" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is where most Interop-based solutions start to break.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Scenario C: Returning a PDF from an ASP.NET Core Endpoint
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;❌ Using Interop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each request spins up a new Office process. Concurrent requests interfere with each other. There's no clean way to write to a response stream — you're saving to disk and reading it back. Process cleanup in an async context is unreliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Using Spire.Doc with MemoryStream&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetReport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"template.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The conversion (handled by Spire.Doc) happens entirely in memory. No temp files, no disk I/O, no process lifecycle to manage. This pattern scales horizontally without any changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3nlgp1de8np13mmc9hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3nlgp1de8np13mmc9hg.png" alt="Spire.Doc for .NET" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is where standalone libraries truly outperform Interop.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Interop vs Modern Libraries: A Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Interop&lt;/th&gt;
&lt;th&gt;Modern Library&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requires Office&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server / Docker Support&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread Safety&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability Under Load&lt;/td&gt;
&lt;td&gt;🔴 Low&lt;/td&gt;
&lt;td&gt;🟢 High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Complexity&lt;/td&gt;
&lt;td&gt;🔴 High&lt;/td&gt;
&lt;td&gt;🟢 Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  When You Might Still Use Interop
&lt;/h2&gt;

&lt;p&gt;Interop isn't universally wrong. It's a reasonable choice for local desktop automation tools, single-user scripts, and scenarios where Office is already present and concurrency isn't a concern. If you need to manipulate macros or preserve highly complex Office-specific formatting, it may still be the only option.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Interop isn't wrong — it's just used in the wrong context.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Conclusion: Move Beyond Interop
&lt;/h2&gt;

&lt;p&gt;Microsoft Office Interop remains convenient for quick solutions, but its reliance on desktop components, lack of scalability, and instability under load make it unsuitable for modern backend systems.&lt;/p&gt;

&lt;p&gt;Today's .NET systems are built to run in cloud environments, containers, and stateless services—where dependencies on GUI-based software simply don't fit. Instead of working around these constraints, it's more effective to adopt tools designed for server-side document processing from the ground up. Standalone libraries—such as Spire.Doc and &lt;a href="https://www.e-iceblue.com/Introduce/pdf-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.PDF&lt;/a&gt; for .NET—exist precisely to address these challenges.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Highlight Duplicates in Excel: A Developer-Friendly Guide</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 13 Apr 2026 09:42:53 +0000</pubDate>
      <link>https://dev.to/jenll/how-to-highlight-duplicates-in-excel-a-developer-friendly-guide-57j8</link>
      <guid>https://dev.to/jenll/how-to-highlight-duplicates-in-excel-a-developer-friendly-guide-57j8</guid>
      <description>&lt;p&gt;If you've ever received a spreadsheet from a product manager and been asked to "just check for duplicates," you know the feeling — it sounds trivial, but the moment you open the file, you realize what you're dealing with: 10,000 rows, multiple columns, no clear definition of what “duplicate” actually means in this context.&lt;/p&gt;

&lt;p&gt;Duplicate detection is one of those tasks that sits at the intersection of Excel power users and backend developers. It's too manual to ignore, yet too tightly coupled to a .xlsx file to fit cleanly into your usual data tooling.&lt;/p&gt;

&lt;p&gt;In this guide, we'll walk through a set of approaches that scale with your needs—from quick UI tricks to automation and developer-friendly solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What Counts as "Duplicates" in Excel?&lt;/li&gt;
&lt;li&gt;Method 1: Conditional Formatting (Built-in UI)&lt;/li&gt;
&lt;li&gt;Method 2: COUNTIF Formula-Driven Conditional Formatting&lt;/li&gt;
&lt;li&gt;Method 3: VBA Macro — Client-Side Automation&lt;/li&gt;
&lt;li&gt;Method 4: Power Query &amp;amp; Dynamic Arrays&lt;/li&gt;
&lt;li&gt;Method 5: Highlight Duplicates in Excel Using C# (Server-Side Automation)&lt;/li&gt;
&lt;li&gt;Method Comparison at a Glance&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Counts as "Duplicates" in Excel?
&lt;/h2&gt;

&lt;p&gt;Before jumping into solutions, it's worth clarifying what we actually mean by "duplicates," because the definition can vary depending on the scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different Types of Duplicates
&lt;/h3&gt;

&lt;p&gt;In most real-world cases, duplicates fall into one of three categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-column duplicates&lt;/strong&gt;: the same value appears more than once in a column, such as repeated email addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-row duplicates&lt;/strong&gt;: every cell in two or more rows is identical. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite duplicates&lt;/strong&gt;: a combination of columns forms a duplicate key, even if no individual column is duplicated on its own. For example, the same '(user_id, date)' pair appearing twice in a transaction log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction matters, because Excel’s built-in tools don’t always handle these cases the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Gotchas
&lt;/h3&gt;

&lt;p&gt;Even when the logic seems straightforward, a few subtle issues can lead to misleading results.&lt;/p&gt;

&lt;p&gt;For example, Excel is &lt;strong&gt;not case-sensitive by default&lt;/strong&gt;, which means &lt;code&gt;Apple&lt;/code&gt; and &lt;code&gt;apple&lt;/code&gt; are treated as the same value. That's convenient in many cases, but it can also hide inconsistencies.&lt;/p&gt;

&lt;p&gt;Hidden characters are another common culprit. A value like &lt;code&gt;"123"&lt;/code&gt; is not the same as &lt;code&gt;"123 "&lt;/code&gt; if there's a trailing space—something that's easy to miss visually.&lt;/p&gt;

&lt;p&gt;You'll also run into &lt;strong&gt;type mismatches&lt;/strong&gt;, where &lt;code&gt;"123"&lt;/code&gt; (text) and &lt;code&gt;123&lt;/code&gt; (number) are treated differently. And yes, even Excel isn’t immune to floating-point quirks—calculations like &lt;code&gt;0.1 + 0.2&lt;/code&gt; may not always behave exactly as expected.&lt;/p&gt;

&lt;p&gt;Taken together, these edge cases are why “finding duplicates” is often more nuanced than it appears.&lt;/p&gt;

&lt;p&gt;&lt;a id="2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Conditional Formatting (Built-in UI)
&lt;/h2&gt;

&lt;p&gt;The fastest way to highlight duplicates — no formulas, no code. Best for one-off checks where you just need to see the problem quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to do it:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select the column or range you want to check, then follow this path:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvft7mq23d416xy7789sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvft7mq23d416xy7789sh.png" alt="Select the column or range" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://support.microsoft.com/en-us/office/use-conditional-formatting-to-highlight-information-in-excel-fed60dfa-1d3f-4e13-9ecb-f1951ff89d7f" rel="noopener noreferrer"&gt;Home → Conditional Formatting → Highlight Cell Rules → Duplicate Values&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4056qs5nryfan1rczntk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4056qs5nryfan1rczntk.png" alt="Highlight Cell Rules" width="800" height="590"&gt;&lt;/a&gt;&lt;br&gt;
A dialog appears letting you choose a highlight color. Click OK and Excel immediately colors every cell whose value appears more than once in your selection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuk6l1x5m0n2iuir8378.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuk6l1x5m0n2iuir8378.png" alt="Duplicate Values" width="800" height="537"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;One behavior to know:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you select multiple columns at once, Excel checks each cell individually — not each row. So if column A has &lt;code&gt;alice&lt;/code&gt; twice and column B has &lt;code&gt;bob&lt;/code&gt; twice, both get highlighted, but a row where &lt;code&gt;(alice, bob)&lt;/code&gt; appears twice would only be caught if both cells independently repeat. If you need row-level or composite duplicate detection, skip ahead to Method 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customizing the format:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The default is a light red fill, but you can set any fill color, font color, or border style. In the same dialog, open the format dropdown and select "Custom Format" to access the full cell formatting options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No case sensitivity control — &lt;code&gt;Alice&lt;/code&gt; and &lt;code&gt;alice&lt;/code&gt; are treated as the same&lt;/li&gt;
&lt;li&gt;Dynamic ranges aren't tracked automatically — if you add rows later, you'll need to reapply or extend the rule manually&lt;/li&gt;
&lt;li&gt;Rules stack: if you have multiple conditional formatting rules on the same range, order matters. Manage them under Home → Conditional Formatting → Manage Rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, for quick, one-off checks, it's often the most efficient option.&lt;/p&gt;

&lt;p&gt;&lt;a id="3"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: COUNTIF Formula-Driven Conditional Formatting
&lt;/h2&gt;

&lt;p&gt;When the built-in UI isn't flexible enough — you need case sensitivity, composite keys, or more precise control over which occurrences get flagged — &lt;a href="https://support.microsoft.com/en-us/office/use-the-countif-function-in-microsoft-excel-e0de10c6-f885-4e71-abb4-1f464816df34" rel="noopener noreferrer"&gt;COUNTIF&lt;/a&gt; gives you that control without leaving Excel.&lt;/p&gt;

&lt;p&gt;The idea is simple: instead of letting Excel decide what counts as a duplicate, you write a formula that returns &lt;code&gt;TRUE&lt;/code&gt; or &lt;code&gt;FALSE&lt;/code&gt; for each cell, and conditional formatting applies the highlight based on your logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select your data range, for example &lt;code&gt;B2:B16&lt;/code&gt;, then create a new conditional formatting rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Home → Conditional Formatting → New Rule → "Use a formula to determine which cells to format."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjutxvyd19xmeauyzzbvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjutxvyd19xmeauyzzbvw.png" alt="New Rule" width="800" height="632"&gt;&lt;/a&gt;&lt;br&gt;
Enter this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=COUNTIF($B$2:$B$16, B2) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy00yts3uvxjeycljqmij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy00yts3uvxjeycljqmij.png" alt="Use a formula to determine which cells to format" width="800" height="665"&gt;&lt;/a&gt;&lt;br&gt;
This counts how many times the value in &lt;code&gt;B2&lt;/code&gt; appears across the entire range. If it appears more than once, the cell gets highlighted. The &lt;code&gt;$&lt;/code&gt; signs lock the range while letting the row reference shift as the rule applies to each cell.&lt;/p&gt;

&lt;p&gt;If you're familiar with SQL, this is the spreadsheet equivalent of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Highlight only the second occurrence onwards:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The basic formula flags every occurrence, including the first. If you want to mark only the duplicates — leaving the original untouched — use this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=COUNTIF($B$2:B2, B2) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference is &lt;code&gt;$B$2:B2&lt;/code&gt; — the start of the range is anchored but the end expands as the rule moves down each row. By the time Excel evaluates row 5, the range is &lt;code&gt;$B$2:B5&lt;/code&gt;, so it only counts occurrences up to the current row. The first appearance is never flagged; only repeats are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composite duplicate detection:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To check whether a combination of columns forms a duplicate — say &lt;code&gt;(email, signup_date)&lt;/code&gt; — concatenate the values into a single key using &lt;code&gt;&amp;amp;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=COUNTIFS($A$2:$A$100, A2, $B$2:$B$100, B2) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with string concatenation across more columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=COUNTIF($A$2:$A$100 &amp;amp; $B$2:$B$100, A2 &amp;amp; B2) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the closest you'll get to a composite primary key check inside native Excel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case-sensitive duplicate detection:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;COUNTIF is case-insensitive by default. For case-sensitive matching, use EXACT wrapped in SUMPRODUCT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=SUMPRODUCT((EXACT($A$2:$A$100, A2)) * 1) &amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This iterates over every value in the range and checks for an exact match, including case, returning a count. More expensive on large datasets, but it works.&lt;/p&gt;

&lt;p&gt;&lt;a id="4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: VBA Macro — Client-Side Automation
&lt;/h2&gt;

&lt;p&gt;The three methods above all live inside Excel's UI. They work well for interactive use, but they're hard to trigger programmatically, difficult to version control, and impossible to run without opening Excel manually. If you need duplicate highlighting to happen automatically — on file open, on a button click, or as part of a repeatable workflow — VBA is the natural next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core logic is straightforward: iterate over a column, track how many times each value has appeared using a &lt;code&gt;Dictionary&lt;/code&gt;, then go back and color any cell whose value appeared more than once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sub HighlightDuplicates()

    Dim ws As Worksheet
    Dim rng As Range
    Dim cell As Range
    Dim dict As Object
    Dim highlightColor As Long

    Set ws = ThisWorkbook.Sheets("Sheet1")
    Set rng = ws.Range("A2:A1000")
    highlightColor = RGB(255, 199, 206)

    Set dict = CreateObject("Scripting.Dictionary")

    For Each cell In rng
        If cell.Value &amp;lt;&amp;gt; "" Then
            Dim key As String
            key = Trim(CStr(cell.Value))
            If dict.exists(key) Then
                dict(key) = dict(key) + 1
            Else
                dict.Add key, 1
            End If
        End If
    Next cell

    For Each cell In rng
        If cell.Value &amp;lt;&amp;gt; "" Then
            If dict(CStr(cell.Value)) &amp;gt; 1 Then
                cell.Interior.Color = highlightColor
            Else
                cell.Interior.ColorIndex = xlNone
            End If
        End If
    Next cell

End Sub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first loop builds the frequency map. The second loop applies or removes the highlight based on the count. Splitting into two passes keeps the logic clean and makes it easy to add a "clear all highlights first" step before reapplying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making it reusable:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hard-coding the sheet name and range is fine for a one-off script, but if you plan to reuse this across files, parameterize the key variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sub HighlightDuplicates(sheetName As String, columnRange As String, highlightColor As Long)
    Dim ws As Worksheet
    Dim rng As Range
    Set ws = ThisWorkbook.Sheets(sheetName)
    Set rng = ws.Range(columnRange)
    ' ... same logic as above
End Sub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from elsewhere in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call HighlightDuplicates("Sheet1", "A2:A1000", RGB(255, 199, 206))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Triggering automatically on file open:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To run the macro every time the workbook opens, add it to the &lt;code&gt;Workbook_Open&lt;/code&gt; event in &lt;code&gt;ThisWorkbook&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private Sub Workbook_Open()
    Call HighlightDuplicates("Sheet1", "A2:A1000", RGB(255, 199, 206))
End Sub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every time someone opens the file, duplicates are flagged automatically — no manual steps required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance on large datasets:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VBA loops are slow by default because Excel recalculates and redraws after every change. For datasets over a few thousand rows, disable these before running and re-enable them after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual

' ... your loop here ...

Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 50,000-row dataset, this alone can reduce runtime from 30+ seconds to 2–3 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VBA falls short:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VBA runs inside Excel, which means it requires Excel to be installed and open. It can't be triggered from a CI pipeline, a backend service, or a scheduled task on a server. If your use case involves processing Excel files without a desktop environment, the next method is what you need.&lt;/p&gt;

&lt;p&gt;&lt;a id="5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: Power Query &amp;amp; Dynamic Arrays
&lt;/h2&gt;

&lt;p&gt;While the previous methods work well within Excel itself, they are harder to integrate into data pipelines or produce clean, structured outputs. If your goal is to output a deduplicated table, feed results into another process, or refresh the analysis automatically when data updates, Power Query and dynamic arrays are better tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic arrays (Excel 365 only):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're on Microsoft 365, the &lt;code&gt;[UNIQUE()](https://support.microsoft.com/en-us/office/unique-function-c5ab87fd-30a3-4ce9-9d1a-40204fb85e1e)&lt;/code&gt; and &lt;code&gt;[FILTER()](https://support.microsoft.com/en-us/office/filter-function-f4f7cb66-82eb-4767-8f7c-4877ad80c759)&lt;/code&gt; functions let you extract duplicate values directly into a new range — no conditional formatting involved.&lt;/p&gt;

&lt;p&gt;To get a list of values that appear more than once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=FILTER(A2:A100, COUNTIF(A2:A100, A2:A100) &amp;gt; 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;COUNTIF(A2:A100, A2:A100)&lt;/code&gt; returns an array of counts — one per row. &lt;code&gt;FILTER()&lt;/code&gt; then keeps only the rows where that count exceeds 1. The result spills automatically into adjacent cells.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: On very large datasets, this approach may become slow because COUNTIF is evaluated across the entire range for each element.&lt;/p&gt;

&lt;p&gt;To get just the unique values among those duplicates — without repetition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=UNIQUE(FILTER(A2:A100, COUNTIF(A2:A100, A2:A100) &amp;gt; 1))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is clean, readable, and updates live as your source data changes. The main constraint is that it requires Excel 365 — it won't work on Excel 2016 or 2019.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power Query:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Power Query works on Excel 2016 and later, and is better suited for larger datasets or more complex transformations. The approach is to group rows by the columns you care about, count occurrences, then filter for groups with more than one row.&lt;/p&gt;

&lt;p&gt;To open Power Query:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Data → Get Data → From Table/Range&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Select your data range and click OK. In the Power Query editor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the column you want to check for duplicates&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Home → Group By&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Group by your target column, add a count column using &lt;strong&gt;Count Rows&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click OK — you now have a summary table with each value and its occurrence count&lt;/li&gt;
&lt;li&gt;Filter the count column to show only rows where count &amp;gt; 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click &lt;strong&gt;Close &amp;amp; Load&lt;/strong&gt; to output the results to a new sheet. Every time you refresh the query (Data → Refresh All), it re-runs against the latest data.&lt;/p&gt;

&lt;p&gt;For composite duplicate detection, select multiple columns before grouping — Power Query will treat the combination as a single key.&lt;/p&gt;

&lt;p&gt;👉 If you're familiar with SQL, you can think of this as a GROUP BY + HAVING COUNT &amp;gt; 1 workflow, but expressed through a visual interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use which:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dynamic arrays are ideal when you want a live formula that updates automatically and the data fits within a single worksheet. Power Query is the better choice when the dataset is large, the transformation logic is complex, or you need a repeatable process that non-technical users can trigger with a single Refresh click.&lt;/p&gt;

&lt;p&gt;Neither method requires code, but both offer a significant step up in capability — and both output clean data rather than just painting cells.&lt;/p&gt;

&lt;p&gt;&lt;a id="6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 5: Highlight Duplicates in Excel Using C# (Server-Side Automation)
&lt;/h2&gt;

&lt;p&gt;Up to this point, all approaches still depend on Excel as the execution environment. That works for manual workflows, but starts to break down when you need to process files in a backend service, a CI/CD pipeline, or any environment where Microsoft Office isn't available.&lt;/p&gt;

&lt;p&gt;In these scenarios, using a standalone .NET library becomes the most practical approach. Instead of relying on Excel itself, you work directly with the file format in code — making the solution easier to automate, deploy, and scale.&lt;/p&gt;

&lt;p&gt;One option here is &lt;a href="https://www.e-iceblue.com/Introduce/excel-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.XLS&lt;/a&gt;, a .NET library that &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Create-Excel-File-in-C-VB.NET.html" rel="noopener noreferrer"&gt;reads and writes Excel files independently of Office&lt;/a&gt;. No COM interop, no Excel process in the background, and no dependency on a desktop environment. You install it via NuGet, point it at a file, and manipulate it like any other object in your codebase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t9ff57p6j7h80kp2z8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t9ff57p6j7h80kp2z8z.png" alt="Spire.XLS for .NET" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install the package via &lt;a href="https://www.nuget.org/packages/Spire.XLS" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Spire.XLS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or via the NuGet Package Manager in Visual Studio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Spire.XLS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No further configuration needed. The library handles &lt;code&gt;.xlsx&lt;/code&gt; and &lt;code&gt;.xls&lt;/code&gt; formats out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core object model:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before writing the implementation, it helps to understand three objects you'll use throughout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Workbook&lt;/code&gt; — represents the entire Excel file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Worksheet&lt;/code&gt; — a single sheet within the workbook&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CellRange&lt;/code&gt; — a cell or range of cells, with properties for value, style, and formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The logic mirrors the VBA approach: build a frequency map, then iterate again to &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Conditional-Formatting/Highlight-Duplicate-and-Unique-Values-in-Excel-Using-C.html" rel="noopener noreferrer"&gt;highlight duplicates&lt;/a&gt;. The difference is that this runs as a standalone C# program — no Excel required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Drawing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;startRow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;endRow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastRow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;targetColumn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Build frequency map&lt;/span&gt;
        &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;valueMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;startRow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;endRow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;CellRange&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetColumn&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;valueMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;valueMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="n"&gt;valueMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Apply highlighting to duplicate rows&lt;/span&gt;
        &lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="n"&gt;highlightColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromArgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;199&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;206&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valueMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;CellRange&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetColumn&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                    &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;highlightColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data_highlighted.xlsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ExcelVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version2013&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done. Duplicates highlighted."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw9gmlqnkjeulaoo5ru8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw9gmlqnkjeulaoo5ru8.png" alt="Highlight Duplicates in Excel Using C#" width="800" height="569"&gt;&lt;/a&gt;&lt;br&gt;
The &lt;code&gt;Dictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt;&lt;/code&gt; stores each unique value alongside the row numbers where it appears. After the first pass, any key with more than one row number in its list is a duplicate. The second pass iterates over those row numbers and applies the fill color.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composite duplicate detection:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To check for duplicates across multiple columns — say columns 1 and 2 together — concatenate the cell values into a composite key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;|&lt;/code&gt; separator prevents false matches where concatenating two different pairs could produce the same string. For example, &lt;code&gt;("ab", "c")&lt;/code&gt; and &lt;code&gt;("a", "bc")&lt;/code&gt; would both produce &lt;code&gt;"abc"&lt;/code&gt; without a separator, but &lt;code&gt;"ab|c"&lt;/code&gt; and &lt;code&gt;"a|bc"&lt;/code&gt; are distinct.&lt;/p&gt;

&lt;p&gt;Make sure the separator character does not appear in your actual data, or choose a more robust delimiter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping the header row:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;startRow = 2&lt;/code&gt; in the implementation above already handles this — adjust the value to match however many header rows your file has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom highlight colors:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Color.FromArgb(r, g, b)&lt;/code&gt; accepts any RGB value. To match the default Excel duplicate highlight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="n"&gt;highlightColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromArgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;199&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;206&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// light red fill&lt;/span&gt;
&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="n"&gt;fontColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromArgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;156&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// dark red text&lt;/span&gt;

&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;highlightColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fontColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Integrating into a .NET service:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because this is plain C#, it slots naturally into any .NET application — an ASP.NET endpoint that processes uploaded files, a background worker that validates reports on a schedule, or a console tool invoked from a CI pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example GitHub Actions step&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Excel report&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet run --project ./ExcelValidator -- --file reports/output.xlsx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process exits with a non-zero code if duplicates are found, which causes the pipeline step to fail — giving you automatic duplicate detection as a CI gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spire.XLS vs. VBA — when to use which:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VBA is the right tool when the work stays on a desktop, the file is opened interactively, and the person running it has Excel. Spire.XLS is the right tool when the file is processed on a server, the environment has no Office installation, or you need the logic to be testable, version-controlled, and integrated into a larger codebase.&lt;/p&gt;

&lt;p&gt;&lt;a id="7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method Comparison at a Glance
&lt;/h2&gt;

&lt;p&gt;Five methods is a lot to hold in your head. Here's a summary to help you pick the right one for your situation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Requires coding&lt;/th&gt;
&lt;th&gt;Needs Office&lt;/th&gt;
&lt;th&gt;Automation&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Conditional Formatting&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Quick one-off visual check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;COUNTIF formula&lt;/td&gt;
&lt;td&gt;No (formula)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Semi-auto&lt;/td&gt;
&lt;td&gt;Flexible logic, stays in Excel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power Query / Dynamic arrays&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (365)&lt;/td&gt;
&lt;td&gt;Auto-refresh&lt;/td&gt;
&lt;td&gt;Periodic reports, clean output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VBA macro&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Full auto&lt;/td&gt;
&lt;td&gt;Desktop automation, event-driven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# Spire.XLS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Full auto&lt;/td&gt;
&lt;td&gt;Server-side, CI/CD, batch processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For quick decision-making, here's a simple rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spot-checking a file someone sent you → &lt;strong&gt;Conditional Formatting&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Logic is complex but you're staying in Excel → &lt;strong&gt;COUNTIF formula&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You need a refreshable, clean output table → &lt;strong&gt;Power Query&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Desktop automation, triggered on open or button click → &lt;strong&gt;VBA&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Server-side, no Office, CI/CD pipeline → &lt;strong&gt;C# Spire.XLS&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Highlighting duplicates in Excel may seem like a small task, but it often reveals deeper issues in how data is structured and validated. What starts as a quick visual check can quickly turn into questions about consistency, edge cases, and reliability. Depending on your workflow, the right approach can range from simple conditional formatting to more controlled, repeatable solutions using formulas, Power Query, or code. As your data grows and processes become more automated, duplicate detection stops being just an Excel trick and becomes part of a broader data quality strategy. Choosing the right method is less about the tools themselves, and more about where your data logic naturally belongs as your workflow evolves.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Export Excel to PDF in .NET (Fast &amp; Accurate Method)</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 30 Mar 2026 07:46:31 +0000</pubDate>
      <link>https://dev.to/jenll/how-to-export-excel-to-pdf-in-net-fast-accurate-method-3d8i</link>
      <guid>https://dev.to/jenll/how-to-export-excel-to-pdf-in-net-fast-accurate-method-3d8i</guid>
      <description>&lt;p&gt;If you’ve ever tried exporting Excel to PDF in a backend service, you probably started with &lt;code&gt;Microsoft.Office.Interop.Excel&lt;/code&gt;. And then it broke.&lt;/p&gt;

&lt;p&gt;No Office is installed on the server. Random crashes under load. Processes that refuse to die. It might work on your machine — but not in production. What you really want is something predictable: no Office dependency, no layout surprises, and something that actually survives in a server or container environment.&lt;/p&gt;

&lt;p&gt;So how do you do that cleanly in .NET?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional Approaches Fall Short
&lt;/h2&gt;

&lt;p&gt;The go-to options for &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Excel-Conversion/NET-Excel-New-method-of-Convert-Excel-to-PDF.html" rel="noopener noreferrer"&gt;Excel-to-PDF conversion in .NET&lt;/a&gt; each come with real trade-offs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Microsoft.Office.Interop.Excel&lt;/code&gt;&lt;/strong&gt; requires a full Office installation on the server — something &lt;a href="https://support.microsoft.com/en-us/topic/considerations-for-server-side-automation-of-office-48bcfe93-8a89-47f1-0bce-017433ad79e2" rel="noopener noreferrer"&gt;Microsoft explicitly advises against&lt;/a&gt; for server-side use. Even when it works, it's single-threaded COM automation running in a multi-request environment: slow, fragile, and prone to leaving zombie processes when something goes wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manual rendering with libraries like iTextSharp&lt;/strong&gt; flips the problem. No Office dependency, but now you're manually reading every cell, recreating styles, handling merged regions, and rebuilding layouts from scratch. The code is brittle, and even after all that work, the output rarely looks like the original.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These approaches either don't scale well or fail to preserve formatting — often both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Need
&lt;/h2&gt;

&lt;p&gt;The real challenge isn't just converting Excel to PDF — it's doing it reliably in a backend environment where Office doesn't exist and formatting still has to look right.&lt;/p&gt;

&lt;p&gt;A practical solution needs to check a few boxes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Microsoft Office dependency&lt;/strong&gt; — must run on a clean server or Linux container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accurate output&lt;/strong&gt; — merged cells, fonts, column widths, and print layout all preserved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles complex files&lt;/strong&gt; — charts, images, and multi-sheet workbooks shouldn't break it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple API&lt;/strong&gt; — integration shouldn't require rebuilding your document rendering logic from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these requirements in mind, let's look at a practical approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started (Minimal Setup)
&lt;/h2&gt;

&lt;p&gt;A practical way to achieve this is by using a dedicated .NET Excel processing library such as &lt;a href="https://www.e-iceblue.com/Introduce/xls-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.XLS for .NET&lt;/a&gt; — a standalone Excel processing library that handles conversion without any Office dependency.&lt;/p&gt;

&lt;p&gt;Install it via &lt;a href="https://www.nuget.org/packages/Spire.XLS/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Spire.XLS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or through the Package Manager Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Install-Package Spire.XLS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting before we write any code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works with .NET 6, 7, 8 and .NET Framework.&lt;/li&gt;
&lt;li&gt;Runs on Windows, Linux, and in Docker containers.&lt;/li&gt;
&lt;li&gt;No Microsoft Office installation required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all the setup needed. Let's convert something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Example: Export Entire Workbook to PDF
&lt;/h2&gt;

&lt;p&gt;Here's the simplest form of the conversion — load a workbook, export to PDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a Workbook object&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Load the Excel file&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Sample.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Option: Fit each sheet to a page on export&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConverterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SheetFitToPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Save as PDF&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Sample.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Release resources&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14tpa59hfb2zudp00a7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14tpa59hfb2zudp00a7v.png" alt="Basic Excel to PDF in C#" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the full conversion. No COM objects, no Office installation, no temp files to clean up. This approach works especially well in background jobs, scheduled tasks, or API-based report generation.&lt;/p&gt;

&lt;p&gt;A few things the library handles automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merged cells — preserved as-is.&lt;/li&gt;
&lt;li&gt;Fonts and cell styles — carried over to the output.&lt;/li&gt;
&lt;li&gt;Column widths and row heights — no reflow or guesswork.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;SheetFitToPage = true&lt;/code&gt; is worth setting by default. Without it, wide sheets may split across pages in ways that break the layout — we'll cover more layout controls in a later section.&lt;/p&gt;

&lt;p&gt;At this point, you already have a reliable Excel-to-PDF pipeline that works without Office and preserves most formatting out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Excel to PDF Conversion Scenarios
&lt;/h2&gt;

&lt;p&gt;The basic example works well — but real-world projects rarely stay that simple. Here are three variations you'll likely run into.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export a Specific Worksheet
&lt;/h3&gt;

&lt;p&gt;Useful when your workbook contains multiple sheets and you only need to export one — a department report, a specific template tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a Workbook object&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Load the Excel file&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Multi-SheetChart.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Get the second worksheet (index starts from 0)&lt;/span&gt;
        &lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Option: Fit the worksheet to a single page (worksheet level)&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesWide&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Fit width to 1 page&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesTall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Height adjusts automatically&lt;/span&gt;

        &lt;span class="c1"&gt;// Save the specific worksheet as PDF&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Multi-SheetChart_Sheet2.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Release resources&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k11pbt1vlgkgn4w62oz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k11pbt1vlgkgn4w62oz.png" alt="Specific Worksheet to PDF in C#" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Export Multiple Worksheets
&lt;/h3&gt;

&lt;p&gt;When you need each sheet as a separate PDF file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a Workbook object and load the Excel file&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Multi-SheetChart.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Get the output directory (same as input file location)&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outputDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDirectoryName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Multi-SheetChart.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Iterate through all worksheets&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="c1"&gt;// Use the worksheet name as the PDF filename&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.pdf"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outputPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Save the worksheet as PDF&lt;/span&gt;
            &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Exported: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Release resources&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nAll worksheets exported successfully!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsrqjc4zvgbfl0xxgslx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsrqjc4zvgbfl0xxgslx.png" alt="C#: Batch Export Multiple Worksheets" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each sheet is saved as an individual file named after the sheet tab. Pair this with &lt;code&gt;Directory.CreateDirectory()&lt;/code&gt; to ensure the output folder exists before running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export a Defined Print Area
&lt;/h3&gt;

&lt;p&gt;If your sheet has a defined print area — say, a template where only rows 10–34 contain actual data — you can export just that range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create a Workbook object&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Load the Excel file&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\StyleSheet.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Get the first worksheet&lt;/span&gt;
        &lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// Define the range to export&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrintArea&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"A10:H34"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Optional: Fit the print area to a single page width&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesWide&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Scale width to 1 page&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesTall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Height automatically adjusts&lt;/span&gt;

        &lt;span class="c1"&gt;// Save the print area as PDF&lt;/span&gt;
        &lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\StyleSheet_PrintArea.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Release resources&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6if8iu0un0n2c9avb7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6if8iu0un0n2c9avb7d.png" alt="Export a Defined Print Area in C#" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is particularly useful for template-based reports where the sheet contains helper formulas or reference data outside the visible range that you don't want included in the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Returning PDF in ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;In a real backend service, you usually don't want to write files to disk — especially in containerized environments where the filesystem may be read-only or ephemeral. Here's how to convert and return the PDF entirely in memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"export"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;ExportToPdf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Load the Excel workbook&lt;/span&gt;
        &lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Ensure file exists in the correct location&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConverterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SheetFitToPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Convert directly to memory — no temp file needed&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Rewind before reading&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Return as file download&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;// Can be optimized (For large files, consider returning the stream directly instead of ToArray())&lt;/span&gt;
            &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"report.pdf"&lt;/span&gt;         &lt;span class="c1"&gt;// triggers download in the browser&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SaveToStream&lt;/code&gt; with &lt;code&gt;FileFormat.PDF&lt;/code&gt; writes directly to the &lt;code&gt;MemoryStream&lt;/code&gt; — no intermediate file on disk.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stream.Position = 0&lt;/code&gt; rewinds the stream before reading; skipping this returns an empty response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Content-Disposition&lt;/code&gt; is handled automatically by ASP.NET Core's &lt;code&gt;File()&lt;/code&gt; result when you pass a filename as the third argument.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern works cleanly in Docker containers, Azure App Service, AWS Lambda, or any environment where writing to the local filesystem isn't an option. It also avoids file I/O overhead, which can become a bottleneck under high concurrency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling Layout for Accurate Output
&lt;/h2&gt;

&lt;p&gt;The conversion works — but on complex sheets, the default settings don't always produce clean output. Wide tables get sliced across pages, margins clip content, and landscape data ends up portrait. These three settings fix most of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Page Size &amp;amp; Orientation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Xls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Workbook&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.xlsx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Worksheet&lt;/span&gt; &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Worksheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Set paper size and orientation&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaperSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaperSizeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaperA4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orientation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PageOrientationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Landscape&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Margins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Values are in inches&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TopMargin&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BottomMargin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LeftMargin&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RightMargin&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tightening the margins gives your content more room to breathe — particularly useful when you're fitting a wide table onto a single page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling (the important one)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Force the sheet to fit within one page wide&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesWide&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PageSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FitToPagesTall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 0 = no vertical limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;FitToPagesWide = 1&lt;/code&gt; is the single most effective setting for preventing wide sheets from splitting mid-column across pages. Setting &lt;code&gt;FitToPagesTall = 0&lt;/code&gt; lets the content grow vertically as needed — you get a clean single-column layout without squashing everything onto one page.&lt;/p&gt;

&lt;p&gt;These three settings mirror what you'd configure in &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Document-Operation/How-to-set-Excel-page-margins-before-printing-a-worksheet-in-C.html" rel="noopener noreferrer"&gt;Excel's &lt;strong&gt;Page Layout&lt;/strong&gt; tab before printing&lt;/a&gt; — if the sheet looks right there, it'll look right in the exported PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Issues &amp;amp; Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fonts render as squares or tofu characters
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; PDF output looks fine on Windows but shows blank boxes where text should be on your CI server or container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The Excel file uses fonts that aren't available in the server environment. The library falls back to a missing font and renders nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Install the missing fonts in your environment, or point the library to a custom font directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Tell Spire.XLS where to find fonts at runtime&lt;/span&gt;
&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomFontPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/usr/share/fonts/custom"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Windows-style fonts on Linux, installing &lt;code&gt;ttf-mscorefonts-installer&lt;/code&gt; via apt usually resolves the most common cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Charts or images missing from the PDF
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Your workbook contains charts or embedded images that simply don't appear in the exported PDF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Chart rendering requires the sheet to be processed in a specific rendering mode. Default settings may skip embedded objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Make sure you're converting via the workbook-level method rather than the worksheet-level one — workbook-level conversion applies full rendering passes, including charts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prefer this for sheets with charts or images&lt;/span&gt;
&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConverterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SheetFitToPage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;workbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layout errors or crashes on Linux / Docker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Everything works locally on Windows, but after deploying to a Linux container you get a &lt;code&gt;DllNotFoundException&lt;/code&gt; or the PDF layout is completely broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Spire.XLS depends on &lt;code&gt;System.Drawing.Common&lt;/code&gt;, which in turn requires &lt;code&gt;libgdiplus&lt;/code&gt; — a native library that isn't included in the default .NET Linux base image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add the following to your Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;        libgdiplus &lt;span class="se"&gt;\
&lt;/span&gt;        libc6-dev &lt;span class="se"&gt;\
&lt;/span&gt;        libx11-dev &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;a href="https://www.e-iceblue.com/forum/exception-the-type-initializer-for-spire-xls-core-spreadsh-t10260.html" rel="noopener noreferrer"&gt;officially recommended setup&lt;/a&gt; for running Spire.XLS in Linux containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-Up
&lt;/h2&gt;

&lt;p&gt;At this point, you’ve got a solid Excel-to-PDF workflow in .NET — no Office dependencies, no fragile COM automation, and no need to rebuild layouts from scratch. It works just as well for background jobs, APIs, or containerized services, and scales from simple exports to more complex reporting scenarios.&lt;/p&gt;

&lt;p&gt;If you're dealing with document generation in your own projects, I’m curious — how are you handling Excel-to-PDF today?&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>productivity</category>
    </item>
    <item>
      <title>OpenXML SDK vs Spire.Doc: Which One Should You Use for Word Processing in C#?</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 23 Mar 2026 09:32:00 +0000</pubDate>
      <link>https://dev.to/jenll/openxml-sdk-vs-spiredoc-which-one-should-you-use-for-word-processing-in-c-5gkj</link>
      <guid>https://dev.to/jenll/openxml-sdk-vs-spiredoc-which-one-should-you-use-for-word-processing-in-c-5gkj</guid>
      <description>&lt;p&gt;Processing Word documents in C# is a common requirement in backend systems such as report generation, document automation, and data extraction.&lt;/p&gt;

&lt;p&gt;For most developers, the choice eventually comes down to two approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open XML SDK&lt;/strong&gt; — Microsoft's low-level library for working directly with &lt;code&gt;.docx&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party libraries like Spire.Doc&lt;/strong&gt; — higher-level APIs focused on productivity and document rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches are widely used in production systems, but they optimize for very different things.&lt;/p&gt;

&lt;p&gt;This article compares them from a practical, server-side perspective — including development complexity, PDF export, deployment concerns, and long-term maintainability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why Word Processing Becomes Complicated on Servers&lt;/li&gt;
&lt;li&gt;Option A: DocumentFormat.OpenXML SDK&lt;/li&gt;
&lt;li&gt;Option B: Using Third-Party Libraries (Spire.Doc)&lt;/li&gt;
&lt;li&gt;Side-by-Side Comparison&lt;/li&gt;
&lt;li&gt;Common Pitfalls and Hidden Traps&lt;/li&gt;
&lt;li&gt;Decision Guide: Which One Should You Choose?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Word Processing Becomes Complicated on Servers
&lt;/h2&gt;

&lt;p&gt;At first glance, generating a &lt;code&gt;.docx&lt;/code&gt; file sounds straightforward.&lt;/p&gt;

&lt;p&gt;In practice, server-side document processing introduces several constraints that significantly affect library selection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Office installation.&lt;/strong&gt; Microsoft explicitly warns against using Word Automation (COM/Interop) in server environments. That rules out the most feature-complete Word automation approach in server environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-platform deployment.&lt;/strong&gt; If your service runs in a Linux container — which is increasingly common in modern deployments — any library with a Windows-only dependency is immediately disqualified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency.&lt;/strong&gt; A server handling concurrent document generation requests needs libraries that behave reliably under concurrent workloads. Some libraries require creating a separate document instance per request to avoid concurrency issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF export.&lt;/strong&gt; "Generate a Word document" almost always means "generate a Word document &lt;em&gt;and&lt;/em&gt; a PDF rendition." The OpenXML SDK has no rendering engine; PDF output requires a separate solution. Some third-party libraries include one.&lt;/p&gt;

&lt;p&gt;These four constraints are what make the OpenXML SDK vs. third-party library decision non-trivial — and why the "just use the free one" instinct doesn't always hold up in production.&lt;/p&gt;

&lt;p&gt;&lt;a id="2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option A: DocumentFormat.OpenXML SDK
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/office/open-xml/open-xml-sdk" rel="noopener noreferrer"&gt;Open XML SDK&lt;/a&gt; is a Microsoft-provided library for working directly with Office Open XML documents such as &lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, and &lt;code&gt;.pptx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of automating Microsoft Word, it manipulates the underlying XML structure directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package DocumentFormat.OpenXml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives developers precise control over document internals — but also exposes the complexity of Word’s document model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Read All Paragraph Text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Packaging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Wordprocessing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReadParagraphs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WordprocessingDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isEditable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainDocumentPart&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;!;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descendants&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descendants&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the cleaner operations in the SDK. &lt;code&gt;Descendants&amp;lt;T&amp;gt;()&lt;/code&gt; traverses the XML tree generically, and the LINQ chain stays readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Replace Template Placeholders &lt;code&gt;{{name}}&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A common server-side pattern: fill a pre-authored &lt;code&gt;.docx&lt;/code&gt; template with runtime data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Packaging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Wordprocessing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MailMerge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordprocessingDocument&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WordprocessingDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainDocumentPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descendants&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{{name}}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{{name}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One important caveat&lt;/strong&gt;: &lt;/p&gt;

&lt;p&gt;Word often splits text across multiple &lt;code&gt;Run&lt;/code&gt; elements internally.&lt;/p&gt;

&lt;p&gt;So a placeholder like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{name}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may actually be stored as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{na
me}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks naive string replacement logic.&lt;/p&gt;

&lt;p&gt;Production-grade implementations usually need additional logic to normalize runs before replacement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Insert a Formatted Table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Packaging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;DocumentFormat.OpenXml.Wordprocessing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InsertTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WordprocessingDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainDocumentPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descendants&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InnerText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The table below presents the sales data for key items."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertAfterSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsertAfterSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableBorders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TopBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BottomBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LeftBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RightBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;InsideHorizontalBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;InsideVerticalBorder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BorderValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)));&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[,]&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Units Sold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Revenue ($)"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1,250"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1,299,000"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"850"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"765,000"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3,200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"92,800"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableRow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunProperties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RunProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableCellProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Shading&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShadingPatternValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Fill&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"4472C4"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunProperties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
                &lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParagraphProperties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ParagraphProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Justification&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JustificationValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TableCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example illustrates the SDK’s core trade-off: precise control at the cost of verbosity. Most formatting operations map directly to the underlying OOXML structure, which provides flexibility at the cost of additional implementation complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;At first glance, the SDK seems manageable. But as soon as you move beyond simple text operations, complexity increases quickly.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Verbose and Low-Level API
&lt;/h4&gt;

&lt;p&gt;Even basic formatting requires navigating multiple layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Paragraph&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RunProperties&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small visual change in Word often translates into a surprisingly large amount of code.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Understanding Document Structure Is Required
&lt;/h4&gt;

&lt;p&gt;To do anything non-trivial, you need to understand how Word structures content internally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How text is split across runs.&lt;/li&gt;
&lt;li&gt;How styles are applied.&lt;/li&gt;
&lt;li&gt;How relationships (&lt;code&gt;rId&lt;/code&gt;) are managed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this, it's easy to produce documents that &lt;em&gt;look fine in code&lt;/em&gt; but break in Word.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. No Built-in Rendering (e.g., PDF)
&lt;/h4&gt;

&lt;p&gt;The SDK itself does not include a rendering engine for PDF conversion.&lt;/p&gt;

&lt;p&gt;To achieve this, you typically need to integrate external tools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LibreOffice (via command line)&lt;/li&gt;
&lt;li&gt;Commercial rendering libraries&lt;/li&gt;
&lt;li&gt;External conversion services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This adds extra infrastructure and deployment complexity, especially in containerized environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  When OpenXML SDK Is the Right Choice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your service runs on Linux or in a container and you cannot take on a commercial dependency.&lt;/li&gt;
&lt;li&gt;You need zero-cost licensing with no per-server or per-document fees.&lt;/li&gt;
&lt;li&gt;You require precise, low-level control over document structure.&lt;/li&gt;
&lt;li&gt;PDF export is not a requirement, or you are prepared to manage a separate rendering pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, OpenXML gives you maximum control — but that control comes with additional implementation complexity.&lt;/p&gt;

&lt;p&gt;&lt;a id="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Option B: Using Third-Party Libraries (Spire.Doc)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.e-iceblue.com/Introduce/word-for-net-introduce.html" rel="noopener noreferrer"&gt;Spire.Doc&lt;/a&gt; is a commercial .NET library developed by E-iceblue.&lt;/p&gt;

&lt;p&gt;Unlike the OpenXML SDK, it does not expose the OOXML object model directly — instead, it provides a document-oriented API that abstracts the XML layer entirely.&lt;/p&gt;

&lt;p&gt;It also ships with its own rendering engine, which means PDF export is built into the library rather than relying on external rendering tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Spire.Doc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A free edition (&lt;code&gt;Spire.Doc for .NET Free&lt;/code&gt;) is available on &lt;a href="https://www.nuget.org/packages/Spire.Doc/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt; without registration. It supports most core features but imposes two hard limits: documents are capped at 3 pages, and a watermark is appended beyond that threshold. Production use requires a commercial license.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Read All Paragraph Text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadAllParagraphs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library aggregates paragraph and run content internally, so developers typically do not need to traverse the underlying node structure manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: Replace Template Placeholders &lt;code&gt;{{name}}&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MailMerge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;templatePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{{name}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Document.Replace()&lt;/code&gt; handles run-splitting internally. The library abstracts this issue internally, so placeholder replacement typically works even when text is split across multiple runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqcylw8wevk1vf72wnil.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqcylw8wevk1vf72wnil.webp" alt="Replace Template Placeholders {{name}}" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Insert a Formatted Table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc.Documents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The table below presents the sales data for key items."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsOneRange&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;OwnerParagraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Owner&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildObjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsOneRange&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;OwnerParagraph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChildObjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.docx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="nf"&gt;CreateTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[,]&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Units Sold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Revenue ($)"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1,250"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1,299,000"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"850"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"765,000"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3,200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"92,800"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResetCells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;AddParagraph&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AppendText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure is comparable in length to the OpenXML SDK version for this particular operation — table construction is inherently row-and-cell iteration regardless of the library. The difference is in the API surface: &lt;code&gt;cell.CellFormat.BackColor&lt;/code&gt; versus constructing a &lt;code&gt;Shading&lt;/code&gt; object with &lt;code&gt;ShadingPatternValues.Clear&lt;/code&gt; and a hex string.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxp9us4tf5obp68z2als.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxp9us4tf5obp68z2als.webp" alt="Insert a Formatted Table" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 4: Export to PDF
&lt;/h3&gt;

&lt;p&gt;The OpenXML SDK does not provide a rendering engine, so PDF export requires integrating external tools (e.g., LibreOffice or commercial renderers).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ExportToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;docxPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pdfPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docxPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdfPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the biggest differences between the two approaches.&lt;/p&gt;

&lt;p&gt;Spire.Doc includes its own rendering engine, which removes the need to integrate external tools such as LibreOffice or Microsoft Word for basic PDF conversion workflows.&lt;/p&gt;

&lt;p&gt;Like most third-party rendering engines, PDF output may not always be pixel-identical to Microsoft Word for highly complex layouts or documents using uncommon fonts.&lt;/p&gt;

&lt;p&gt;For large-scale batch conversion scenarios, memory usage and throughput testing are still recommended, especially in containerized deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Spire.Doc
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Higher-Level API
&lt;/h4&gt;

&lt;p&gt;Most operations map directly to how developers think about documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sections.&lt;/li&gt;
&lt;li&gt;Paragraphs.&lt;/li&gt;
&lt;li&gt;Tables.&lt;/li&gt;
&lt;li&gt;Styles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids exposing most OOXML-specific implementation details in application code.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Rich Feature Support
&lt;/h4&gt;

&lt;p&gt;Common real-world requirements are handled natively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.e-iceblue.com/Tutorials/Spire.Doc/Spire.Doc-Program-Guide/How-to-Convert-Word-to-PDF.html" rel="noopener noreferrer"&gt;Word → PDF&lt;/a&gt; / HTML / Image conversion.&lt;/li&gt;
&lt;li&gt;Complex table layouts.&lt;/li&gt;
&lt;li&gt;Headers, footers, styles.&lt;/li&gt;
&lt;li&gt;Images and formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Reduced Implementation Overhead
&lt;/h4&gt;

&lt;p&gt;Tasks that require dozens of lines in OpenXML can often be implemented in just a few lines here.&lt;/p&gt;

&lt;p&gt;This difference becomes more significant as document complexity increases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-offs to Consider
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Licensing
&lt;/h4&gt;

&lt;p&gt;The free version includes limitations.&lt;/p&gt;

&lt;p&gt;For production use, a commercial license is typically required.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Less Low-Level Control
&lt;/h4&gt;

&lt;p&gt;Since the library abstracts away the XML layer, fine-grained control over document internals is more limited compared to OpenXML.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Dependency Size and Runtime Considerations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Larger package size than OpenXML.&lt;/li&gt;
&lt;li&gt;Cold start impact in serverless or container environments.&lt;/li&gt;
&lt;li&gt;Closed-source — debugging internal rendering or layout issues may be harder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, migrating away later may require refactoring document-generation logic tied to the library API.&lt;/p&gt;

&lt;h3&gt;
  
  
  When It Makes Sense to Use Third-Party Libraries
&lt;/h3&gt;

&lt;p&gt;This approach is often a better fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;document conversion (e.g., Word → PDF)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You are dealing with &lt;strong&gt;complex layouts or formatting&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;reduce development time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Your project has a &lt;strong&gt;budget for commercial components&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, third-party libraries trade low-level control for productivity — and in many real-world applications, that trade-off is often acceptable.&lt;/p&gt;

&lt;p&gt;&lt;a id="4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side Comparison
&lt;/h2&gt;

&lt;p&gt;The previous two sections walked through identical operations with both libraries. Here is a consolidated view across the dimensions that matter most for a production decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;OpenXML SDK&lt;/th&gt;
&lt;th&gt;Spire.Doc (Free)&lt;/th&gt;
&lt;th&gt;Spire.Doc (Commercial)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DOCX read/write&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Placeholder replacement&lt;/td&gt;
&lt;td&gt;✅ ⚠️&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table insertion&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mail merge&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF export&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ ⚠️&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML export&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image insertion&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Header / footer editing&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document encryption&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page limit&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;3 pages&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watermark on output&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (&amp;gt;3 pages)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;⚠️ OpenXML SDK placeholder replacement requires manual run consolidation to handle split text nodes.&lt;br&gt;&lt;br&gt;
⚠️ Spire.Doc Free appends a watermark on documents exceeding 3 pages.&lt;/p&gt;
&lt;h3&gt;
  
  
  Operational Cost Comparison
&lt;/h3&gt;

&lt;p&gt;This is the dimension developers most commonly overlook during library selection.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost factor&lt;/th&gt;
&lt;th&gt;OpenXML SDK&lt;/th&gt;
&lt;th&gt;Spire.Doc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Library license&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free tier / paid commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF rendering&lt;/td&gt;
&lt;td&gt;LibreOffice or equivalent required&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure overhead&lt;/td&gt;
&lt;td&gt;LibreOffice process management&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux font setup&lt;/td&gt;
&lt;td&gt;Not required&lt;/td&gt;
&lt;td&gt;Required for non-Latin fonts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal visibility&lt;/td&gt;
&lt;td&gt;Full source access&lt;/td&gt;
&lt;td&gt;Limited to exposed APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The OpenXML SDK's zero license cost is real, but "free" does not account for the engineering time spent implementing PDF export, managing a LibreOffice process in containers, and handling edge cases in the OOXML object model. In a commercial project with a team of more than two developers, that time has a measurable cost.&lt;/p&gt;

&lt;p&gt;&lt;a id="5"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Common Pitfalls and Hidden Traps
&lt;/h2&gt;

&lt;p&gt;No matter which approach you choose, there are a few non-obvious issues that can cause serious problems in production.&lt;/p&gt;

&lt;p&gt;Here are some of the most common ones.&lt;/p&gt;
&lt;h3&gt;
  
  
  OpenXML SDK Pitfalls
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Broken Documents Due to Relationship ID Conflicts
&lt;/h4&gt;

&lt;p&gt;When adding images, styles, or other parts, each element is linked using a relationship ID (&lt;code&gt;rId&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Hardcoding or reusing IDs can lead to corrupted files:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Word found unreadable content…”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;👉 &lt;strong&gt;Best practice:&lt;/strong&gt;&lt;br&gt;
Always use built-in methods to generate IDs instead of manually assigning them.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Text Replacement Isn’t as Simple as It Looks
&lt;/h4&gt;

&lt;p&gt;In Word, text is often split across multiple &lt;code&gt;Run&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;This breaks naive string replacement logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normalize runs before replacement.&lt;/li&gt;
&lt;li&gt;Or use more robust search strategies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Spire.Doc Pitfalls
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Font Issues in Linux Environments
&lt;/h4&gt;

&lt;p&gt;When running in Docker or Linux containers, missing fonts can cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Garbled text.&lt;/li&gt;
&lt;li&gt;Missing characters.&lt;/li&gt;
&lt;li&gt;Layout inconsistencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
Install necessary fonts in your container, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fonts-wqy-zenhei
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Limited Debugging Visibility
&lt;/h4&gt;

&lt;p&gt;Since the library is closed-source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal processing is not visible&lt;/li&gt;
&lt;li&gt;Debugging rendering or layout issues can be harder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 You may need to rely on documentation or vendor support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Pitfall: Security Risks (XXE)
&lt;/h3&gt;

&lt;p&gt;Like many document-processing workflows, handling untrusted user-uploaded files can introduce security risks if parsing and validation are not handled carefully.&lt;/p&gt;

&lt;p&gt;A common risk is &lt;strong&gt;XML External Entity (XXE) injection&lt;/strong&gt;, which can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data leakage&lt;/li&gt;
&lt;li&gt;Remote file access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mitigation strategies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use secure XML parsing settings (e.g., prohibit DTD processing).&lt;/li&gt;
&lt;li&gt;Validate and sanitize uploaded files.&lt;/li&gt;
&lt;li&gt;Avoid processing untrusted documents directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Most issues don’t come from “wrong APIs” — they come from assumptions about how Word documents behave internally.&lt;/p&gt;

&lt;p&gt;Understanding these pitfalls early can save hours of debugging and prevent production incidents.&lt;/p&gt;

&lt;p&gt;&lt;a id="6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision Guide: Which One Should You Choose?
&lt;/h2&gt;

&lt;p&gt;At this point, the choice between the Open XML SDK and a third-party library like Spire.Doc should be clearer — but let’s make it more practical.&lt;/p&gt;

&lt;p&gt;Instead of abstract comparison, here’s a simple decision flow based on real-world needs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Decision Flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you need PDF or other format conversion?&lt;/strong&gt;&lt;br&gt;
→ Yes → A third-party library is often the simpler option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you have a budget for commercial components?&lt;/strong&gt;&lt;br&gt;
→ No → Use OpenXML (optionally combined with external tools like LibreOffice).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is your document logic simple (e.g., text replacement, basic structure)?&lt;/strong&gt;&lt;br&gt;
→ Yes → OpenXML is sufficient.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you need to handle complex layouts, tables, or styling?&lt;/strong&gt;&lt;br&gt;
→ Yes → Third-party libraries often reduce implementation time for complex document workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Are you targeting Linux containers or fully open-source stacks?&lt;/strong&gt;&lt;br&gt;
→ Yes → OpenXML is often the simplest fully open-source option.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Flexible Approach (Best of Both Worlds)
&lt;/h3&gt;

&lt;p&gt;In some projects, you don’t have to commit to a single solution.&lt;/p&gt;

&lt;p&gt;A common strategy is to define an abstraction layer, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IWordProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ReplaceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;InsertTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ExportToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;inputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Then provide different implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OpenXmlWordProcessor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SpireWordProcessor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with one approach&lt;/li&gt;
&lt;li&gt;Switch later if requirements change&lt;/li&gt;
&lt;li&gt;Test performance or cost trade-offs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Processing Word documents in C# can range from simple text manipulation to complex document generation pipelines.&lt;/p&gt;

&lt;p&gt;The Open XML SDK gives you full control over the document structure, but requires a deeper understanding of OOXML and more development effort. Third-party libraries like Spire.Doc simplify many common tasks and reduce implementation overhead, especially for feature-rich scenarios.&lt;/p&gt;

&lt;p&gt;The decision is less about which API is “better” and more about which operational complexity your team wants to own.&lt;/p&gt;

&lt;p&gt;OpenXML gives you maximum control and zero licensing cost, but shifts more implementation responsibility onto your application. Higher-level libraries reduce that complexity, but introduce dependency and licensing considerations in return.&lt;/p&gt;

&lt;p&gt;For production systems, making the right architectural decision early can significantly reduce maintenance overhead, deployment complexity, and long-term implementation cost.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Convert Word to PDF in C# Without Microsoft Office (Fast &amp; Server-Safe)</title>
      <dc:creator>Chloe</dc:creator>
      <pubDate>Mon, 16 Mar 2026 09:40:48 +0000</pubDate>
      <link>https://dev.to/jenll/how-to-convert-word-to-pdf-in-c-without-microsoft-office-fast-server-safe-4004</link>
      <guid>https://dev.to/jenll/how-to-convert-word-to-pdf-in-c-without-microsoft-office-fast-server-safe-4004</guid>
      <description>&lt;p&gt;If you've ever tried to convert Word to PDF in C# using &lt;strong&gt;Microsoft Office Interop&lt;/strong&gt;, you probably know the pain.&lt;/p&gt;

&lt;p&gt;COM dependency issues, unstable server-side automation, and the requirement to install the full &lt;strong&gt;Microsoft Office&lt;/strong&gt; suite just to perform document conversion.&lt;/p&gt;

&lt;p&gt;This approach might work on a local machine, but it quickly becomes problematic in production environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Office automation is &lt;strong&gt;not recommended for server-side applications&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It introduces &lt;strong&gt;heavy dependencies&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It often causes &lt;strong&gt;performance and stability issues&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what's the better way?&lt;/p&gt;

&lt;p&gt;In this guide, we'll show how to convert Word documents to PDF in C# &lt;strong&gt;without installing Microsoft Office&lt;/strong&gt;, using &lt;a href="https://www.e-iceblue.com/Introduce/word-for-net-introduce.html" rel="noopener noreferrer"&gt;&lt;strong&gt;Spire.Doc for .NET&lt;/strong&gt;&lt;/a&gt; - a lightweight .NET library designed for programmatic Word processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Spire.Doc for .NET for Word to PDF Conversion?
&lt;/h2&gt;

&lt;p&gt;Before we write any code, let's address the obvious question: &lt;em&gt;why Spire.Doc specifically?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Unlike Office automation, it allows developers to work with Word documents &lt;strong&gt;directly through a managed .NET API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a quick comparison of common approaches:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Spire.Doc&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Word Interop&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;LibreOffice Headless&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Office required&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;td&gt;❌ Full install&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux / Docker&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes (via CLI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversion fidelity&lt;/td&gt;
&lt;td&gt;✅ High&lt;/td&gt;
&lt;td&gt;✅ High&lt;/td&gt;
&lt;td&gt;⚠️ Variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server-side safe&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ Not recommended&lt;/td&gt;
&lt;td&gt;⚠️ Via subprocess&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline / air-gapped&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;✅ In-process&lt;/td&gt;
&lt;td&gt;🐢 Slow (COM)&lt;/td&gt;
&lt;td&gt;🐢 Process spin-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;One-time license&lt;/td&gt;
&lt;td&gt;Office license&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is pretty clear. Word Interop is the go-to for desktop apps where Office is already present - but it's &lt;a href="https://learn.microsoft.com/en-us/office/troubleshoot/office-developer/consider-using-third-party-component" rel="noopener noreferrer"&gt;explicitly not recommended by Microsoft&lt;/a&gt; for server-side use. LibreOffice headless is a solid free option, but spawning a new process per conversion under load creates its own reliability headaches.&lt;/p&gt;

&lt;p&gt;Spire.Doc hits the sweet spot for server-side automation: it's a single NuGet package, fully managed .NET, no external processes, no Office, no network calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8018iz2j3yrzhq23ymcq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8018iz2j3yrzhq23ymcq.webp" alt="Spire.Doc for .NET" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes it a reliable solution for &lt;strong&gt;modern .NET applications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡Tip:&lt;/strong&gt; Spire.Doc for .NET offers a free version for evaluation and small projects. A commercial license is required for full features and production use.&lt;/p&gt;

&lt;p&gt;🚀 If your .NET application runs in Linux containers, make sure the libgdiplus package is installed. It provides the GDI+ compatibility layer required for document rendering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup: Install Spire.Doc
&lt;/h2&gt;

&lt;p&gt;Installation only takes a few seconds using &lt;a href="https://www.nuget.org/packages/Spire.Doc" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PM&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Install-Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Spire.Doc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, you're ready to start converting Word documents programmatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Conversion - Word to PDF in C
&lt;/h2&gt;

&lt;p&gt;Let's start with the simplest scenario: converting &lt;strong&gt;a .docx&lt;/strong&gt; file to &lt;strong&gt;PDF&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ConvertWordToPdf&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Load a Word document&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C:\\Users\\Tommy\\Desktop\\Sample.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Save the document to PDF&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C:\\Users\\Tommy\\Desktop\\Sample.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Dispose resources&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Example output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w58u9t6dh0qfecnn8i5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4w58u9t6dh0qfecnn8i5.webp" alt="Convert Word to PDF in C#" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code performs three steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load the Word document into memory.&lt;/li&gt;
&lt;li&gt;Render the document layout.&lt;/li&gt;
&lt;li&gt;Export the rendered content as a PDF file.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Advanced Word to PDF Conversion Settings
&lt;/h2&gt;

&lt;p&gt;The default conversion works well, but production systems often need more control over the output. Spire.Doc provides several options that allow developers to tune PDF generation for different scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stream-based Conversion - for Web API responses
&lt;/h3&gt;

&lt;p&gt;If you're building an API endpoint that returns a PDF download, writing to disk first is wasteful. Spire.Doc can write directly to any &lt;strong&gt;Stream&lt;/strong&gt;, which means you can pipe the output straight to the HTTP response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"convert"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;ConvertToPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFormFile&lt;/span&gt; &lt;span class="n"&gt;wordFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wordFile&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;wordFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No file uploaded."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wordFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;outputStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Load from upload stream directly — no temp file needed&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Rewind before returning&lt;/span&gt;
    &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;outputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;//&lt;/span&gt;
        &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileNameWithoutExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wordFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".pdf"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Points Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;doc.LoadFromStream(inputStream, FileFormat.Docx);&lt;/strong&gt; - Loads Word document directly from upload stream, no temp file needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;doc.SaveToStream(outputStream, FileFormat.PDF);&lt;/strong&gt; - Writes converted PDF directly to memory stream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;outputStream.Position = 0;&lt;/strong&gt; - Resets stream position before returning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;return File(outputStream, "application/pdf", fileName);&lt;/strong&gt; - Streams PDF directly to HTTP response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Note on &lt;strong&gt;MemoryStream&lt;/strong&gt; and large files: For documents under ~50 MB this pattern works fine. For larger files in high-throughput scenarios, consider writing to a &lt;strong&gt;FileStream&lt;/strong&gt; on a temp path and streaming that back - holding very large documents in &lt;strong&gt;MemoryStream&lt;/strong&gt; will put pressure on the LOH (Large Object Heap).&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF output options - page size, image quality, font embedding
&lt;/h3&gt;

&lt;p&gt;The default output is good, but Spire.Doc exposes a ToPdfParameterList object that gives you control over the most commonly tuned settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Pdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ConvertWordToPdf&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Load a Word document&lt;/span&gt;
                &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Tommy\Desktop\Sample.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Create PDF parameter list for output options&lt;/span&gt;
                &lt;span class="n"&gt;ToPdfParameterList&lt;/span&gt; &lt;span class="n"&gt;pdfParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ToPdfParameterList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                &lt;span class="c1"&gt;// 1. Set page size&lt;/span&gt;
                &lt;span class="c1"&gt;// Options: A1, A2, A3, A4, A5, Letter, Legal, etc.&lt;/span&gt;
                &lt;span class="n"&gt;pdfParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PdfPageSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfPageSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// 2. Set image quality (0-100, higher = better quality, larger file size)&lt;/span&gt;
                &lt;span class="n"&gt;pdfParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageQuality&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 80% quality, good balance&lt;/span&gt;

                &lt;span class="c1"&gt;// 3. Set font embedding&lt;/span&gt;
                &lt;span class="n"&gt;pdfParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsEmbeddedAllFonts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Embed all fonts for consistent display&lt;/span&gt;
                &lt;span class="c1"&gt;// Alternative: pdfParams.IsEmbeddedFonts = true; // Some versions use this&lt;/span&gt;

                &lt;span class="c1"&gt;// Optional: Set PDF conformance level (for long-term archiving)&lt;/span&gt;
                &lt;span class="c1"&gt;// pdfParams.PdfConformanceLevel = PdfConformanceLevel.Pdf_A1B;&lt;/span&gt;

                &lt;span class="c1"&gt;// Optional: Set compression level&lt;/span&gt;
                &lt;span class="c1"&gt;// pdfParams.CompressionLevel = PdfCompressionLevel.Best;&lt;/span&gt;

                &lt;span class="c1"&gt;// Save the document to PDF with custom parameters&lt;/span&gt;
                &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\Users\Tommy\Desktop\Sample.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdfParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PDF created successfully with custom options!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Code Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ToPdfParameterList&lt;/strong&gt; - Object that configures PDF output settings like page size, image quality, and font embedding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PdfPageSize.A4&lt;/strong&gt; - Sets the output page dimensions (A4, Letter, Legal, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ImageQuality = 80&lt;/strong&gt; - Controls image compression (0-100); 80 balances quality and file size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IsEmbeddedAllFonts&lt;/strong&gt; = true - Embeds all fonts to ensure cross-platform display consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Batch conversion - process an entire folder
&lt;/h3&gt;

&lt;p&gt;For report generation pipelines, scheduled exports, or migration jobs, you'll typically need to process multiple files. Here's a production-ready pattern with basic error isolation so one bad file doesn't abort the whole batch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"C:\Users\Tommy\Desktop\WordFiles"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;targetDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"C:\Users\Tommy\Desktop\PDFFiles"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*.docx"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pdfFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileNameWithoutExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;".pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdfFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Convert: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;document archiving systems.&lt;/li&gt;
&lt;li&gt;automated report generation.&lt;/li&gt;
&lt;li&gt;contract processing pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Points Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Directory.CreateDirectory(targetDir);&lt;/strong&gt; - Ensures output folder exists before saving files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory.GetFiles(sourceDir, "*.docx")&lt;/strong&gt; - Retrieves all Word documents from the source folder for batch processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path.Combine(targetDir, ...)&lt;/strong&gt; - Constructs output file path while preserving filename.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path.GetFileNameWithoutExtension(file) + ".pdf"&lt;/strong&gt; - Generates PDF filename by replacing the original extension.&lt;/li&gt;
&lt;li&gt;Creating a new &lt;strong&gt;Document&lt;/strong&gt; instance per file ensures resources are released and prevents memory buildup during large batch jobs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real-World Scenarios: ASP.NET Core Web API
&lt;/h2&gt;

&lt;p&gt;The most common server-side pattern: a client POSTs a &lt;strong&gt;.docx&lt;/strong&gt;, gets a &lt;strong&gt;.pdf&lt;/strong&gt; back in the same response. Here's a complete, production-ready controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Spire.Doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WordToPdfController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFormFile&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadFromStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveToStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is commonly used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;document generation platforms.&lt;/li&gt;
&lt;li&gt;SaaS reporting systems.&lt;/li&gt;
&lt;li&gt;automated invoice processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[Route("api/[controller]")]&lt;/strong&gt; and &lt;strong&gt;[ApiController]&lt;/strong&gt; - Standard ASP.NET Core attributes that define the API endpoint and enable automatic model validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;await file.CopyToAsync(ms);&lt;/strong&gt; - Asynchronously copies uploaded file to memory stream for processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ms.Position = 0;&lt;/strong&gt; and &lt;strong&gt;pdf.Position = 0;&lt;/strong&gt; - Resets stream positions to beginning before reading or returning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;doc.LoadFromStream(ms, FileFormat.Docx);&lt;/strong&gt; - Loads Word document directly from memory stream without saving to disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;doc.SaveToStream(pdf, FileFormat.PDF);&lt;/strong&gt; - Converts and writes PDF to memory stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;return File(pdf, "application/pdf", "output.pdf");&lt;/strong&gt; - Streams PDF directly back to client as downloadable file.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Common Pitfalls Developers Hit When Converting Word to PDF
&lt;/h2&gt;

&lt;p&gt;When implementing &lt;a href="https://www.e-iceblue.com/Tutorials/Spire.Doc/Spire.Doc-Program-Guide/How-to-Convert-Word-to-PDF.html" rel="noopener noreferrer"&gt;Word-to-PDF conversion in C#&lt;/a&gt; in production, there are a few common issues developers run into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing Fonts on Linux ServersLinux&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your application runs on Linux, missing fonts can cause layout problems.&lt;/p&gt;

&lt;p&gt;Install common fonts using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fontconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also need to install additional font packages depending on your document templates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large Document Memory Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large documents with high-resolution images can consume significant memory.&lt;/p&gt;

&lt;p&gt;Possible solutions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reducing image resolution.&lt;/li&gt;
&lt;li&gt;streaming processing pipelines.&lt;/li&gt;
&lt;li&gt;batch processing instead of single large jobs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Layout Differences&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some complex Word features (especially embedded objects) may render slightly differently in PDF.&lt;/p&gt;

&lt;p&gt;Testing with real-world documents is always recommended.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;If you're building a C# application that needs Word-to-PDF conversion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid Microsoft Office Interop for server environments.&lt;/li&gt;
&lt;li&gt;Use a dedicated document library like Spire.Doc for .NET.&lt;/li&gt;
&lt;li&gt;Implement stream-based or batch processing for scalable systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With just a few lines of code, you can integrate reliable Word-to-PDF conversion into your .NET applications - without installing Microsoft Office.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
