C#: Mit itext7 ein PDF in einen MemoryStream schreiben

Ein wenig Kontext

Da wir demnächst verpflichtet sind E-Rechnungen (Factur-X/ZUGFeRD) anzubieten, mussten wir unsere Rechnungserstellung entsprechend erweitern. Wir nutzen LaTeX um aus einem Template ein PDF für den visuellen Teil einer Rechnung zu generieren.Um die Rechnungen nun um E-Rechnungen zu erweitern nutze ich ZUGFeRD-csharp um das dazu gehörende XML zu erstellen. Außerdem nutze ich itext7 um diese beiden Teile zusammen zu führen. Ich muss das PDF dann in einen Blob Storage hoch laden. Deshalb muss ich es in C# mit itext7 in einen MemoryStream schreiben. Allerdings bin ich dabei auf ein Problem gestoßen, zu dem es etwas schwierig war eine dokumentierte Lösung zu finden. Eben weil das so schwierig war, habe ich diesen Blogbeitrag geschrieben.

Das Problem

Die Dokumentation von itext7 erwähnt zwar, dass man PDFs auch in Streams schreiben kann, anstatt direkt in Dateien, übergeht dabei allerdings ein wichtiges Detail. Man muss die PdfWriter-Instanz explizit so konfigurieren, dass sie den ihr übergebenen Stream nicht Disposed. Wenn man dies nicht tut, bevor man PdfDocument#Close() aufruft, wird das PDF zwar in den Stream geschrieben, dann wird allerdings der Stream auch sofort Disposed. Das heißt die Daten sind Weg und nicht mehr lesbar. Die eine Variante, in der das auch ohne die extra Konfiguration funktioniert, ist wenn man einen FileStream benutzt. Das funktioniert deshalb, weil ein FileStream seine Daten in die dazu gehörende Datei auf dem Speichermedium schreibt, sobald sie in ihn geschrieben werden.

Jede Dokumentation, die ich gefunden habe gibt einem Beispiel-Code wie das folgende. Zu dest wird oft gesagt, dass es ein string mit einem Dateipfad sein kann. Es kann allerdings auch ein Stream sein, wie zum Beispiel HttpResponse.OutputStream oder ein MemoryStream. Gerade bei einem HTTP-Response Stream könnte es allerdings auch fatal sein, wenn dieser vorzeitig Disposed wird.

// Beispiel A: Neues PDF-Dokument erstellen
var newWriter = new PdfWriter(dest);
var newPdfDocument = new PdfDocument(newWriter);
var newDocument = new Document(newPdfDocument):
// Mach irgendetwas mit dem PDF.
newDocument.Close();

// Beispiel B: Ein bestehendes PDF-Dokument manipulieren
var existingPdfDocument = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
// Mach irgendetwas mit dem PDF.
existingPdfDocument.Close();

Die Lösung

Die Lösung kam mir dann nach langem Suchen. Ich fand sie in einem Kommentar(!) zu einer Antwort auf StackOverflow: PdfWriter#SetCloseStream(false)
Diese Methode ist in der itext7-Dokumentation nicht einmal auffindbar. Selbst andere StackOverflow Fragen und Antworten oder auch andere Blogbeiträge zu diesem Thema erwähnen diese Methode nur als Randerscheinung.

Mein fertiger Code sieht jetzt in etwa so aus:

using (var pdfOutputStream = new MemoryStream())
{
    var writer = new PdfWriter(pdfOutputStream);

    // DAS HIER IST DER WICHTIGE METHODENAUFRUF!
    writer.SetCloseStream(false);

    var pdfDocument = new PdfADocument(new PdfReader(src), writer);

    // Mach irgendetwas mit dem PDF.

    // Mit diesem Methodenaufruf schreibt itext7 den PDF-Inhalt in den
    // `pdfOutputStream`. Ohne `SetCloseStream(false)` wird der Stream hier auch
    // gleich `Dispose`d.
    pdfDocument.Close();

    // Wir müssen den `Stream` auch wieder "zurück spulen", um von ihm lesen zu
    // können.
    pdfOutputStream.Seek(0, SeekOrigin.Begin);

    // Lese den `pdfOutputStream` mit irgendetwas anderem, wie zum Beispiel
    // einem Blob-Store-Client.
}

Damit habe ich erfolgreich in C# mit itext7 ein PDF in einen MemoryStream geschrieben.

0 Kommentare

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert