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 Stream
s 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 Dispose
d. 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 Dispose
d. 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 Dispose
d 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.
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!