.NET アプリケーションで PDF ファイルをマージする必要がありますか?
ASP.NET Core、Docker、Azure App Service、または Linux で実行している場合、Microsoft Office や Interop に依存することは選択肢になりません。Office オートメーションはサーバーサイドのワークロード向けに設計されたものではなく、Microsoft は無人環境での Office アプリケーションの使用を明示的に推奨していません。
幸いなことに、PDF のマージは完全にマネージドコードで実行できます。
実際、マージ自体は通常わずか数行のコードで済みます。より難しいのは、本番環境のワークロードに耐えられるようにすることです。
このガイドでは、以下の方法を学びます:
- C# で複数の PDF をマージする方法
- 一般的な PDF マージの落とし穴を回避する方法
- 大きなドキュメントを安全に処理する方法
- ASP.NET Core で PDF マージ API を構築する方法
- ページ選択、透かし、ページ番号付けを追加してマージ PDF を拡張する方法
まずは最もシンプルな実装から始めましょう。
クイックスタート:C# で複数の PDF をマージする
以下の例では、Spire.PDF を使用します。同じ概念は、最新のほとんどの .NET PDF ライブラリに適用されます。
パッケージをインストールします:
dotnet add package Spire.PDF
次に、C# で複数の PDF ファイルをマージします。わずか数行のコードです:
using Spire.Pdf;
namespace MergePDFs
{
class Program
{
static void Main(string[] args)
{
// マージする PDF ファイルを指定
string[] files = new string[] {"sample0.pdf", "sample1.pdf", "sample2.pdf"};
// PDF ファイルをマージ
PdfDocumentBase pdf = PdfDocument.MergeFiles(files);
// 結果ファイルを保存
pdf.Save("MergePDF.pdf", FileFormat.PDF);
}
}
}
基本的なマージにはこれで十分です。
ただし、実際のアプリケーションでは、ファイルが大きくなったり、異なるドキュメントソースや本番環境のワークロードが関わってくると、PDF マージはしばしばより複雑になります。
開発者が Office オートメーションから離れる理由
多くの開発者は、最初に Microsoft Office Interop を通じてドキュメントオートメーションに遭遇します。
ローカルマシンではうまく動作することがよくあります。
その後、アプリケーションがデプロイされます。
すると突然、次のような問題が発生し始めます:
- Office のインストールがない
- 権限の問題
- ランダムな COM 例外
- 終了しないプロセス
- 高いメモリ消費
- コンテナ内でのデプロイの頭痛
PDF マージに Office はまったく必要ないため、Office 依存関係を導入することは、何の利点もなく複雑さを増すだけです。
専用の PDF ライブラリは、一般的により信頼性が高く、デプロイが容易で、サーバー環境により適しています。
一般的な PDF マージの問題(とその修正方法)
シンプルな API でも、PDF マージはいくつかの予測可能な方法で失敗することがあります。このセクションでは、最も一般的な問題を取り上げます — 特に混乱を引き起こしがちな問題から始めます。
間違いその 1:PDF オブジェクトの破棄を忘れる
マージされたファイルは生成されるものの、開こうとすると PDF リーダーがエラーを表示する — またはファイルサイズが不自然に小さいように見えます。
なぜ発生するのか
最も一般的な原因は、SaveToFile() または Close() が呼び出されていないか、間違った順序で呼び出されていることです。ファイルが適切に確定される前にプログラムが終了したり例外が発生したりすると、出力が不完全になります。
修正方法
使用後は必ずリソースを解放してください。
sourceDocument.Close();
mergedDocument.Close();
👉 ヒント:適切な破棄を確実にするために、可能な限り using ステートメントを使用してください。
間違いその 2:フォントが常に存在すると想定する
マージされた PDF 内のテキストが文字化けしたり、代替フォントで表示されたり、まったく表示されなかったりする — 元のファイルは問題なく見えていたにもかかわらず。
なぜ発生するのか
PDF はフォントを埋め込まずに参照している場合があります。実行環境でそれらのフォントが利用できない場合、マージ結果が正しく表示されないことがあります。
修正方法
最も確実な解決策は、マージ前にソース PDF にフォントが埋め込まれていることを確認することです。これにより、環境間で一貫したレンダリングが保証されます。
ソースファイルを制御できない場合は、一貫した環境を使用してファイルを処理およびマージし、システムインストール済みフォントに依存しないようにしてください。
PdfDocument mergedDocument = new PdfDocument();
foreach (string file in inputFiles)
{
PdfDocument temp = new PdfDocument(file);
mergedDocument.AppendPage(temp);
temp.Close();
}
mergedDocument.SaveToFile(outputFile);
mergedDocument.Close();
👉 本番環境では、常に単純な PDF だけでなく実際のドキュメントでテストしてください。
間違いその 3:すべての PDF をメモリに読み込む
マージは小さいファイルでは問題なく動作しますが、入力ファイルが大きいか多数ある場合に、System.OutOfMemoryException がスローされる — または顕著なメモリ圧力が発生します。
なぜ発生するのか
デフォルトでは、PdfDocument を読み込むとファイル全体がメモリに読み込まれます。10 個の 50MB PDF をマージする場合、出力ドキュメントが書き込まれる前に、潜在的に 500MB を一度に保持していることになります。
修正方法
ファイルを一度に 1 つずつ処理し、次のファイルを読み込む前にそれぞれを解放してください。すべてのソースドキュメントを同時に開いたままにしないでください:
PdfDocument merged = new PdfDocument();
foreach (string file in inputFiles)
{
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
PdfDocument temp = new PdfDocument(fs);
merged.AppendPage(temp);
temp.Close();
}
}
merged.SaveToFile(outputFile);
merged.Close();
さらに進んだ対策
非常に大きなバッチの場合は、チャンク単位でのマージを検討してください — ファイルを 5~10 個のグループにまとめ、各チャンクを一時ファイルに書き込み、それから一時ファイルの最終マージを行います。これにより、入力サイズに関係なくピークメモリ使用量を抑えられます。
大量のボリュームを効率的に処理する
大量ボリューム向けの非同期バッチ処理
一度に多くのファイルを処理するパイプラインでは、I/O でスレッドをブロックしないようにしてください。以下はシンプルな非同期ラッパーです:
public async Task<byte[]> MergePdfsAsync(IEnumerable<string> filePaths)
{
// Spire.PDF の MergeFiles は非同期 I/O をサポートしていないため、
// 実際には async ラッパーを省略するか、キャッシングレイヤーを追加するだけでも構いません
return await Task.Run(() =>
{
var filesArray = filePaths.ToArray();
// 公式に推奨されている MergeFiles メソッドを使用
using var mergedDocument = PdfDocument.MergeFiles(filesArray);
using var output = new MemoryStream();
mergedDocument.SaveToStream(output);
mergedDocument.Close();
return output.ToArray();
});
}
高スループットのシナリオでは、これをキュー(例:Azure Service Bus やシンプルな Channel<T>)と組み合わせて、マージジョブを並列ではなく一度に 1 つずつ処理してください — PDF マージはメモリ集約型であり、並行処理は逆効果になる可能性があります。
実世界シナリオ:マージを ASP.NET Core API エンドポイントとして公開する
PDF アップロードを受け付けてマージファイルを返すサービスを構築する場合、以下は最小限のエンドポイント構造です:
app.MapPost("/merge-stream", async (HttpRequest request) =>
{
var files = request.Form.Files;
if (files.Count < 2)
return Results.BadRequest("少なくとも 2 つのファイルが必要です。");
PdfDocument merged = new PdfDocument();
try
{
foreach (var file in files)
{
using var stream = file.OpenReadStream();
// ストリームから PDF を読み込む
using var tempDoc = new PdfDocument(stream);
// すべてのページを挿入
for (int i = 0; i < tempDoc.Pages.Count; i++)
{
merged.InsertPage(tempDoc, i, i);
}
}
using var output = new MemoryStream();
merged.SaveToStream(output);
merged.Close();
output.Position = 0;
return Results.File(output.ToArray(), "application/pdf", "merged.pdf");
}
catch (Exception ex)
{
merged.Close();
return Results.Problem($"PDF マージ中にエラーが発生しました: {ex.Message}");
}
});
本番環境での使用にあたり、いくつか注意すべき点があります:
- 処理前にファイルタイプを検証する
- アップロードごとに適切なサイズ制限を設定する
- 大きなファイルの場合は、リクエストスレッドをブロックする代わりにバックグラウンドジョブでマージを実行することを検討する
基本的なマージを超えて
多くの実世界のワークフローでは、PDF のマージは最初のステップに過ぎません。
最終ドキュメントが組み立てられた後、開発者は特定のページの選択、透かしの適用、ページ番号の追加などの追加操作を必要とすることがよくあります。最新のほとんどの PDF ライブラリは、マージワークフローに大きな変更を加えることなくこれらのタスクをサポートしています。
特定のページのみをマージする
ドキュメント全体ではなく、各ソースファイルから特定のページ範囲のみをマージする必要がある場合もあります。
典型的な実装は次のようになります:
PdfDocument newPDF = new PdfDocument();
// 選択したページを挿入
newPDF.InsertPageRange(pdfs[0], 1, 2); // 1 つ目の PDF の 1~2 ページ
newPDF.InsertPage(pdfs[1], 0); // 2 つ目の PDF の最初のページ
newPDF.InsertPage(pdfs[2], 1); // 3 つ目の PDF の 2 ページ目
newPDF.SaveToFile("SelectivePageMerging.pdf");
これは、複数のレポートから特定のセクションを抽出して 1 つの要約ドキュメントにまとめる場合に便利です。
マージ中に透かしを追加する
各ページに会社名、「CONFIDENTIAL」、ドラフトラベルなどの透かしを押す必要がある場合、マージステップはそれを行うのに自然な場所です。
foreach (var page in pdf.Pages)
{
page.Canvas.SetTransparency(0.5f);
page.Canvas.TranslateTransform(page.Canvas.Size.Width / 2, page.Canvas.Size.Height / 2);
page.Canvas.RotateTransform(-45);
page.Canvas.DrawString("CONFIDENTIAL", font, PdfBrushes.DarkGray, -150, -25);
page.Canvas.SetTransparency(1f);
}
透明度、フォントサイズ、位置をレイアウトに合わせて調整してください。
マージ後にページ番号を追加する
ページ番号は元のドキュメントではなく最終ドキュメントを反映する必要があるため、マージ完了後に追加します。
簡略化した実装は次のようになります:
for (int i = 0; i < totalPages; i++)
{
PdfPageBase page = pdf.Pages[i];
string pageNumberText = $"Page {i + 1} of {totalPages}";
SizeF textSize = font.MeasureString(pageNumberText);
float x = page.Canvas.ClientSize.Width - textSize.Width - 50f;
float y = page.Canvas.ClientSize.Height - 50f;
page.Canvas.DrawString(pageNumberText, font, PdfBrushes.Black, x, y);
}
PDF ライブラリの選択
単純な PDF マージの場合、最新のほとんどの .NET ライブラリで目的を達成できます。
違いが顕著になるのは、高度な機能、商用サポート、特定のライセンスモデルが必要な場合です。
簡単な目安は次のとおりです:
-
Spire.PDF — エンタープライズ向け PDF ドキュメント処理ソリューション
-
PDFsharp — 基本的な PDF ワークフローに適した優れた無料オプション
-
iText 7 — 高度なドキュメント操作やエンタープライズ要件に最適
-
Aspose.PDF — 大規模なドキュメント処理プロジェクト向けの多機能な選択肢
ほとんどの ASP.NET Core アプリケーションでは、マージ API 自体よりも、安定していて積極的にメンテナンスされており、ライセンス要件に対応したライブラリを選ぶことの方が重要です。
よくある質問
Linux で PDF をマージできますか?
はい。最新のほとんどの .NET PDF ライブラリは、Microsoft Office を必要とせずに Linux で動作します。
Docker コンテナ内で PDF をマージできますか?
はい。専用の PDF ライブラリは、一般的に Office オートメーションよりもコンテナ化されたデプロイメントに適しています。
Microsoft Office をインストールする必要がありますか?
いいえ。PDF マージは完全にマネージドコードで実行できます。
Microsoft.Office.Interop は ASP.NET Core に適していますか?
一般的に、いいえ。Microsoft は無人サーバー環境での Office オートメーションの使用を推奨していません。ASP.NET Core アプリケーションでは、専用の PDF ライブラリの方が一般的に信頼性が高く、デプロイも容易です。
PDF マージにおける最大の課題は何ですか?
本番環境では、メモリ使用量とドキュメント品質(フォントや埋め込みリソースなど)が、マージ操作自体よりも問題を引き起こすことが一般的です。
まとめ
ASP.NET Core で PDF をマージするために必要な実際のコードは驚くほど少ないです。
本番環境で問題を引き起こすのは、通常、マージ操作自体ではなく、その周辺のすべてです:
- リソースのクリーンアップ
- メモリ使用量
- フォントの可用性
- アップロードの検証
Office 依存関係を回避し、これらの一般的な落とし穴を理解することで、ASP.NET Core、Docker コンテナ、クラウド環境で確実に動作する PDF マージサービスを構築できます。
適切な PDF ライブラリを使用すれば、ASP.NET Core での PDF マージは Microsoft Office から完全に独立させることができ、サーバー、コンテナ、クラウドプラットフォーム全体でのデプロイがよりシンプルで確実になります。




