Renamed methods across multiple client classes for consistency, shortening and aligning naming conventions (e.g., `CheckAttachmentsAsync` → `CheckAsync`, `ValidatePdfAsync` → `ValidateAsync`). Updated corresponding interfaces, unit tests, and documentation to reflect the new method names. Standardized method signatures to support both `Stream` and `byte[]` overloads consistently. Improved error handling in tests and updated examples in the README for batch processing and validation scenarios. Enhanced API endpoint overview for clarity.
1175 lines
41 KiB
Markdown
1175 lines
41 KiB
Markdown
# DocumentService.Client
|
||
|
||
.NET-Clientbibliothek für die DocumentService-API – unterstützt .NET Framework 4.6.2, 4.8 und .NET 8.0.
|
||
|
||
## Funktionsumfang
|
||
|
||
- **Multi-Target-Unterstützung**: .NET Framework 4.6.2, 4.8 und .NET 8.0
|
||
- **Statischer Einstiegspunkt**: `Client`-Klasse – kein DI-Container erforderlich
|
||
- **Separate Clients pro Fachbereich**: `Client.Validation`, `Client.Attachment`, `Client.Operations`, `Client.SwissQrCode`, `Client.Zugferd`, `Client.Conversion`
|
||
- **Dualer Eingabe-Support**: Multipart (Stream) und Base64 (byte[]) für alle Endpunkte
|
||
- **Stark typisierte Modelle**: Gemeinsame Request/Response-DTOs mit XML-Dokumentation
|
||
- **Workflows**: `Client.Workflows` – kombinierte Operationen mit vorgelagerter Validierung (z. B. Swiss QR Code mit automatischer PDF-Prüfung)
|
||
|
||
## Installation
|
||
|
||
Das Paket wird über einen internen NuGet-Feed bereitgestellt und ist **nicht** auf nuget.org verfügbar.
|
||
|
||
**NuGet-Paket:** `http://172.24.12.35:5000/packages/documentservice.client/1.0.0`
|
||
**API-Basis-URL:** `http://172.24.12.39:9393/`
|
||
**Swagger-Dokumentation:** [http://172.24.12.39:9393/swagger/index.html](http://172.24.12.39:9393/swagger/index.html)
|
||
|
||
### .NET-Projekt (Visual Studio)
|
||
|
||
Paket über den NuGet Package Manager installieren. Dabei sicherstellen, dass der interne NuGet-Server (`http://172.24.12.35:5000/nuget`) als Paketquelle eingetragen und ausgewählt ist.
|
||
|
||
### PowerShell (ohne .NET-Projekt)
|
||
|
||
Für PowerShell-Skripte ohne zugehöriges .NET-Projekt muss die Assembly manuell heruntergeladen und eingebunden werden.
|
||
|
||
**Schritt 1 – Paket herunterladen und entpacken:**
|
||
|
||
```powershell
|
||
# NuGet-Paket herunterladen (.nupkg ist ein ZIP-Archiv)
|
||
$nupkgUrl = "http://172.24.12.35:5000/packages/documentservice.client/1.0.0"
|
||
$nupkgPath = "$env:TEMP\DocumentService.Client.1.0.0.nupkg"
|
||
$extractDir = "$env:TEMP\DocumentService.Client"
|
||
|
||
Invoke-WebRequest -Uri $nupkgUrl -OutFile $nupkgPath
|
||
|
||
# Entpacken (nupkg = ZIP)
|
||
Expand-Archive -Path $nupkgPath -DestinationPath $extractDir -Force
|
||
```
|
||
|
||
**Schritt 2 – Assembly für die Zielplattform laden:**
|
||
|
||
```powershell
|
||
# Für PowerShell 7+ (.NET 8 / net8.0)
|
||
$dllPath = Join-Path $extractDir "lib\net8.0\DocumentService.Client.dll"
|
||
|
||
# Für Windows PowerShell 5.1 (.NET Framework 4.8 / net48)
|
||
# $dllPath = Join-Path $extractDir "lib\net48\DocumentService.Client.dll"
|
||
|
||
Add-Type -Path $dllPath
|
||
```
|
||
|
||
**Schritt 3 – Abhängigkeiten laden** (falls nicht bereits im GAC / PowerShell-Runspace vorhanden):
|
||
|
||
```powershell
|
||
# Abhängige Assemblies aus demselben lib-Verzeichnis laden
|
||
$libDir = Split-Path $dllPath -Parent
|
||
Get-ChildItem -Path $libDir -Filter "*.dll" | ForEach-Object {
|
||
try { Add-Type -Path $_.FullName } catch { <# bereits geladen #> }
|
||
}
|
||
```
|
||
|
||
> **Hinweis:** In der Praxis empfiehlt es sich, die extrahierten DLLs einmalig in ein festes Verzeichnis (z. B. `C:\Tools\DocumentService.Client\`) zu kopieren und `Add-Type` mit einem absoluten Pfad aufzurufen. Das verhindert wiederholte Downloads und Versionskonflikte zwischen Skript-Ausführungen.
|
||
|
||
---
|
||
|
||
## Konfiguration
|
||
|
||
Diese Dokumentation beschreibt die Nutzung über den statischen `Client`-Einstiegspunkt, der ohne DI-Container auskommt. Die Library unterstützt darüber hinaus vollständige `IServiceCollection`-Integration via `AddDocumentServiceClients()` – dies ist jedoch nicht Gegenstand dieser Dokumentation.
|
||
|
||
### Einmalige Initialisierung (z. B. in `Program.cs` oder `App_Start`)
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Kurzform – nur Base-URL
|
||
Client.Configure("http://172.24.12.39:9393/");
|
||
|
||
// Vollständige Konfiguration
|
||
Client.Configure(options =>
|
||
{
|
||
options.BaseUrl = "http://172.24.12.39:9393/";
|
||
options.Timeout = TimeSpan.FromMinutes(10);
|
||
options.MaxRetries = 3;
|
||
options.ThrowOnError = true;
|
||
});
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
' Kurzform – nur Base-URL
|
||
Client.Configure("http://172.24.12.39:9393/")
|
||
|
||
' Vollständige Konfiguration
|
||
Client.Configure(Sub(options)
|
||
options.BaseUrl = "http://172.24.12.39:9393/"
|
||
options.Timeout = TimeSpan.FromMinutes(10)
|
||
options.MaxRetries = 3
|
||
options.ThrowOnError = True
|
||
End Sub)
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
# Kurzform – nur Base-URL
|
||
[DocumentService.Client.Client]::Configure("http://172.24.12.39:9393/")
|
||
|
||
# Vollständige Konfiguration
|
||
[DocumentService.Client.Client]::Configure([Action[DocumentService.Client.Configuration.DocumentServiceClientOptions]] {
|
||
param($options)
|
||
$options.BaseUrl = "http://172.24.12.39:9393/"
|
||
$options.Timeout = [TimeSpan]::FromMinutes(10)
|
||
$options.MaxRetries = 3
|
||
$options.ThrowOnError = $true
|
||
})
|
||
```
|
||
|
||
`Client.Configure()` ist **idempotent im Sinne der Initialisierung**: Der interne Service Provider wird beim ersten Zugriff lazy gebaut und danach nicht mehr verändert. Ein zweiter `Configure()`-Aufruf wird standardmäßig mit einer `InvalidOperationException` abgelehnt. Dieses Verhalten ist über `Client.OnReconfigure` steuerbar:
|
||
|
||
```csharp
|
||
// Standard – wirft bei erneutem Configure()-Aufruf
|
||
Client.OnReconfigure = OnReconfigure.ThrowException;
|
||
|
||
// Zweiten Aufruf stillschweigend ignorieren (z. B. in Bibliotheks-Code)
|
||
Client.OnReconfigure = OnReconfigure.Ignore;
|
||
```
|
||
|
||
```vbnet
|
||
' Standard – wirft bei erneutem Configure()-Aufruf
|
||
Client.OnReconfigure = OnReconfigure.ThrowException
|
||
|
||
' Zweiten Aufruf stillschweigend ignorieren
|
||
Client.OnReconfigure = OnReconfigure.Ignore
|
||
```
|
||
|
||
```powershell
|
||
# Standard – wirft bei erneutem Configure()-Aufruf
|
||
[DocumentService.Client.Client]::OnReconfigure = [DocumentService.Client.Models.ValueObjects.OnReconfigure]::ThrowException
|
||
|
||
# Zweiten Aufruf stillschweigend ignorieren
|
||
[DocumentService.Client.Client]::OnReconfigure = [DocumentService.Client.Models.ValueObjects.OnReconfigure]::Ignore
|
||
```
|
||
|
||
### Verfügbare Client-Eigenschaften
|
||
|
||
| Eigenschaft | Interface | Fachbereich |
|
||
|---|---|---|
|
||
| `Client.Validation` | `IPdfValidationClient` | PDF-Validierung |
|
||
| `Client.Attachment` | `IPdfAttachmentClient` | PDF-Anhänge |
|
||
| `Client.Operations` | `IPdfOperationsClient` | PDF-Operationen |
|
||
| `Client.SwissQrCode` | `ISwissQrCodeClient` | Schweizer QR-Code |
|
||
| `Client.Zugferd` | `IZugferdClient` | ZUGFeRD-Rechnungen |
|
||
| `Client.Conversion` | `IPdfConversionClient` | PDF-Konvertierung *(geplant)* |
|
||
| `Client.Workflows` | `IWorkflowsClient` | Kombinierte Operationen |
|
||
|
||
Jeder Zugriff auf eine dieser Eigenschaften erstellt einen neuen DI-Scope. Clients sind **nicht** für die dauerhafte Speicherung als Felder gedacht – jeder Aufruf holt sich eine frische Instanz.
|
||
|
||
> **PowerShell-Hinweis:** PowerShell unterstützt `async/await` nicht nativ. Alle asynchronen Methoden werden über `.GetAwaiter().GetResult()` synchron blockierend aufgerufen. Für lang laufende Operationen (z. B. große PDFs) empfiehlt sich die Ausführung in einem eigenen Thread via `[System.Threading.Tasks.Task]::Run({...}).GetAwaiter().GetResult()`.
|
||
|
||
---
|
||
|
||
## Konfigurationsoptionen
|
||
|
||
| Eigenschaft | Typ | Standardwert | Beschreibung |
|
||
|---|---|---|---|
|
||
| `BaseUrl` | `string` | `"http://localhost:5000"` | Basis-URL der DocumentService-API |
|
||
| `Timeout` | `TimeSpan` | `TimeSpan.FromMinutes(5)` | HTTP-Anfrage-Timeout |
|
||
| `MaxRetries` | `int` | `3` | Maximale Wiederholungsversuche bei transienten Fehlern |
|
||
| `ThrowOnError` | `bool` | `true` | Exception bei HTTP 4xx/5xx-Antworten |
|
||
|
||
---
|
||
|
||
## Verwendungsbeispiele
|
||
|
||
Alle Beispiele nutzen ausschließlich den statischen `Client`-Einstiegspunkt. Voraussetzung ist eine einmalige Konfiguration wie oben beschrieben.
|
||
|
||
---
|
||
|
||
### Workflows – Kombinierte Operationen
|
||
|
||
`Client.Workflows` fasst häufig benötigte Abläufe zusammen, die mehrere Einzelendpunkte orchestrieren. Die Validierung läuft dabei immer zuerst – die Folgeaktion wird nur bei einem gültigen Dokument ausgeführt.
|
||
|
||
> Für Anwendungsfälle wie den **eParser** (Swiss QR Bill aus Eingangsrechnungen lesen) ist dies der empfohlene Einstiegspunkt.
|
||
|
||
#### Swiss QR Code – mit vorgelagerter Validierung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Variante A: byte[]
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("rechnung.pdf");
|
||
var result = await Client.Workflows.ExtractAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Betrag: {result?.Bill?.Amount} {result?.Bill?.Currency}");
|
||
Console.WriteLine($"Empfänger: {result?.Bill?.Creditor?.Name}");
|
||
|
||
// Variante B: Stream
|
||
using var stream = File.OpenRead("rechnung.pdf");
|
||
var result2 = await Client.Workflows.ExtractAsync(stream);
|
||
|
||
// Variante C: Dateipfad
|
||
var result3 = await Client.Workflows.ExtractAsync("rechnung.pdf");
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
' Variante A: byte[]
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("rechnung.pdf")
|
||
Dim result = Await Client.Workflows.ExtractAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Betrag: {result?.Bill?.Amount} {result?.Bill?.Currency}")
|
||
Console.WriteLine($"Empfänger: {result?.Bill?.Creditor?.Name}")
|
||
|
||
' Variante B: Dateipfad
|
||
Dim result2 = Await Client.Workflows.ExtractAsync("rechnung.pdf")
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
# Variante A: byte[]
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Workflows.ExtractAsync($pdfBytes).GetAwaiter().GetResult()
|
||
Write-Host "Betrag: $($result.Bill.Amount) $($result.Bill.Currency)"
|
||
Write-Host "Empfaenger:$($result.Bill.Creditor.Name)"
|
||
|
||
# Variante B: Dateipfad
|
||
$result2 = [DocumentService.Client.Client]::Workflows.ExtractAsync("rechnung.pdf").GetAwaiter().GetResult()
|
||
```
|
||
|
||
**Fehlerbehandlung – `throwIfInvalid`:**
|
||
|
||
Standardmäßig wirft die Methode eine `InvalidOperationException`, wenn das PDF ungültig ist. Mit `throwIfInvalid: false` wird stattdessen `null` zurückgegeben – geeignet, wenn ungültige Dokumente kein Ausnahmefall sind und ohne try/catch behandelt werden sollen.
|
||
|
||
```csharp
|
||
// Exception (Standard) – geeignet, wenn ungültige PDFs ein Fehlerfall sind
|
||
var result = await Client.Workflows.ExtractAsync(pdfBytes);
|
||
|
||
// Null zurückgeben – geeignet für Batch-Verarbeitung o. Ä.
|
||
var result = await Client.Workflows.ExtractAsync(pdfBytes, throwIfInvalid: false);
|
||
if (result is null)
|
||
{
|
||
Console.WriteLine("Dokument ungültig oder verschlüsselt – übersprungen.");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
' Exception (Standard)
|
||
Dim result = Await Client.Workflows.ExtractAsync(pdfBytes)
|
||
|
||
' Null zurückgeben
|
||
Dim result2 = Await Client.Workflows.ExtractAsync(pdfBytes, throwIfInvalid:=False)
|
||
If result2 Is Nothing Then
|
||
Console.WriteLine("Dokument ungültig oder verschlüsselt – übersprungen.")
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
# Exception (Standard)
|
||
$result = [DocumentService.Client.Client]::Workflows.ExtractAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
# Null zurückgeben ($false = throwIfInvalid:false)
|
||
$result2 = [DocumentService.Client.Client]::Workflows.ExtractAsync($pdfBytes, $false, $false).GetAwaiter().GetResult()
|
||
if ($null -eq $result2) {
|
||
Write-Host "Dokument ungueltig oder verschluesselt – uebersprungen."
|
||
}
|
||
```
|
||
|
||
> **Signatur:** `ExtractSwissQrCodeAsync(pdfBytes / Stream / filePath, raw = false, throwIfInvalid = true, ct = default)`
|
||
> `raw: true` liefert die Rohtextzeilen des QR-Codes ohne Parsing zurück (siehe [Schweizer QR-Code-Extraktion](#4-schweizer-qr-code-extraktion)).
|
||
|
||
---
|
||
|
||
### 1. PDF-Validierung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Variante A: Stream (multipart/form-data – empfohlen für große Dateien)
|
||
using var stream = File.OpenRead("rechnung.pdf");
|
||
var result = await Client.Validation.ValidateAsync(stream);
|
||
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
Console.WriteLine($"PDF-Version: {result.PdfVersion}");
|
||
Console.WriteLine($"Verschlüsselt: {result.IsEncrypted}");
|
||
Console.WriteLine($"Anhänge: {result.AttachmentCount}");
|
||
Console.WriteLine($"Dateigröße: {result.FileSizeMB:F2} MB");
|
||
|
||
// Variante B: byte[] (Base64 JSON)
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("rechnung.pdf");
|
||
var result2 = await Client.Validation.ValidateAsync(pdfBytes);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
' Variante A: Stream
|
||
Dim stream As New System.IO.FileStream("rechnung.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidateAsync(stream)
|
||
stream.Dispose()
|
||
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
Console.WriteLine($"PDF-Version: {result.PdfVersion}")
|
||
Console.WriteLine($"Verschlüsselt: {result.IsEncrypted}")
|
||
Console.WriteLine($"Anhänge: {result.AttachmentCount}")
|
||
Console.WriteLine($"Dateigröße: {result.FileSizeMB:F2} MB")
|
||
|
||
' Variante B: byte[]
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("rechnung.pdf")
|
||
Dim result2 = Await Client.Validation.ValidateAsync(pdfBytes)
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
# Variante A: Stream
|
||
$stream = [System.IO.File]::OpenRead("rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidateAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
Write-Host "PDF-Version: $($result.PdfVersion)"
|
||
Write-Host "Verschluesselt:$($result.IsEncrypted)"
|
||
Write-Host "Anhaenge: $($result.AttachmentCount)"
|
||
Write-Host "Dateigroesse: $($result.FileSizeMB.ToString('F2')) MB"
|
||
|
||
# Variante B: byte[]
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("rechnung.pdf")
|
||
$result2 = [DocumentService.Client.Client]::Validation.ValidateAsync($pdfBytes).GetAwaiter().GetResult()
|
||
```
|
||
|
||
---
|
||
|
||
#### PDF/A-Konformitätsprüfung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
using var stream = File.OpenRead("archiv.pdf");
|
||
var result = await Client.Validation.ValidatePdfAAsync(stream);
|
||
|
||
Console.WriteLine($"PDF/A-konform: {result.IsValid}");
|
||
Console.WriteLine($"PDF/A-Version: {result.PdfAVersion}");
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
|
||
if (result.Errors.Count > 0)
|
||
{
|
||
Console.WriteLine("Konformitätsfehler:");
|
||
foreach (var error in result.Errors)
|
||
Console.WriteLine($" - {error}");
|
||
}
|
||
|
||
if (result.Warnings.Count > 0)
|
||
{
|
||
Console.WriteLine("Warnungen:");
|
||
foreach (var warning in result.Warnings)
|
||
Console.WriteLine($" - {warning}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim stream As New System.IO.FileStream("archiv.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidatePdfAAsync(stream)
|
||
stream.Dispose()
|
||
|
||
Console.WriteLine($"PDF/A-konform: {result.IsValid}")
|
||
Console.WriteLine($"PDF/A-Version: {result.PdfAVersion}")
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
|
||
If result.Errors.Count > 0 Then
|
||
Console.WriteLine("Konformitätsfehler:")
|
||
For Each [error] In result.Errors
|
||
Console.WriteLine($" - {[error]}")
|
||
Next
|
||
End If
|
||
|
||
If result.Warnings.Count > 0 Then
|
||
Console.WriteLine("Warnungen:")
|
||
For Each warning In result.Warnings
|
||
Console.WriteLine($" - {warning}")
|
||
Next
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$stream = [System.IO.File]::OpenRead("archiv.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidatePdfAAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
|
||
Write-Host "PDF/A-konform: $($result.IsValid)"
|
||
Write-Host "PDF/A-Version: $($result.PdfAVersion)"
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
|
||
if ($result.Errors.Count -gt 0) {
|
||
Write-Host "Konformitaetsfehler:"
|
||
foreach ($err in $result.Errors) { Write-Host " - $err" }
|
||
}
|
||
|
||
if ($result.Warnings.Count -gt 0) {
|
||
Write-Host "Warnungen:"
|
||
foreach ($warn in $result.Warnings) { Write-Host " - $warn" }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. PDF-Anhänge
|
||
|
||
#### Anhänge erkennen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("dokument.pdf");
|
||
var check = await Client.Attachment.CheckAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Hat Anhänge: {check.HasAttachments}");
|
||
Console.WriteLine($"Anzahl: {check.AttachmentCount}");
|
||
|
||
foreach (var attachment in check.Attachments)
|
||
Console.WriteLine($" {attachment.FileName} ({attachment.Size} Bytes, {attachment.MimeType})");
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("dokument.pdf")
|
||
Dim check = Await Client.Attachment.CheckAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Hat Anhänge: {check.HasAttachments}")
|
||
Console.WriteLine($"Anzahl: {check.AttachmentCount}")
|
||
|
||
For Each attachment In check.Attachments
|
||
Console.WriteLine($" {attachment.FileName} ({attachment.Size} Bytes, {attachment.MimeType})")
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("dokument.pdf")
|
||
$check = [DocumentService.Client.Client]::Attachment.CheckAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Hat Anhaenge: $($check.HasAttachments)"
|
||
Write-Host "Anzahl: $($check.AttachmentCount)"
|
||
|
||
foreach ($att in $check.Attachments) {
|
||
Write-Host " $($att.FileName) ($($att.Size) Bytes, $($att.MimeType))"
|
||
}
|
||
```
|
||
|
||
#### Anhänge extrahieren
|
||
|
||
Rückgabe: `Dictionary<string, Stream>` – Schlüssel = ursprünglicher Dateiname im PDF, Wert = Dateiinhalt als `MemoryStream`. Der Aufrufer ist für die Freigabe der Streams verantwortlich.
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
using var pdfStream = File.OpenRead("dokument_mit_anhaengen.pdf");
|
||
var files = await Client.Attachment.ExtractAsync(pdfStream);
|
||
|
||
foreach (var (fileName, fileStream) in files)
|
||
{
|
||
await using var fs = fileStream;
|
||
var outputPath = Path.Combine("output", fileName);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
||
await using var output = File.Create(outputPath);
|
||
await fs.CopyToAsync(output);
|
||
Console.WriteLine($"Extrahiert: {outputPath}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfStream As New System.IO.FileStream("dokument_mit_anhaengen.pdf", System.IO.FileMode.Open)
|
||
Dim files = Await Client.Attachment.ExtractAsync(pdfStream)
|
||
pdfStream.Dispose()
|
||
|
||
For Each entry In files
|
||
Dim outputPath = System.IO.Path.Combine("output", entry.Key)
|
||
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath))
|
||
Using fs = entry.Value
|
||
Using output = System.IO.File.Create(outputPath)
|
||
Await fs.CopyToAsync(output)
|
||
End Using
|
||
End Using
|
||
Console.WriteLine($"Extrahiert: {outputPath}")
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfStream = [System.IO.File]::OpenRead("dokument_mit_anhaengen.pdf")
|
||
$files = [DocumentService.Client.Client]::Attachment.ExtractAsync($pdfStream).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
New-Item -ItemType Directory -Force -Path "output" | Out-Null
|
||
|
||
foreach ($entry in $files.GetEnumerator()) {
|
||
$outputPath = Join-Path "output" $entry.Key
|
||
$dir = Split-Path $outputPath -Parent
|
||
if ($dir) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
||
|
||
$outFile = [System.IO.File]::Create($outputPath)
|
||
$entry.Value.CopyTo($outFile)
|
||
$outFile.Dispose()
|
||
$entry.Value.Dispose()
|
||
|
||
Write-Host "Extrahiert: $outputPath"
|
||
}
|
||
```
|
||
|
||
> **Hinweis – `AddAsync`:** Das Einbetten von Anhängen in PDF/A-3-Dokumente ist für eine zukünftige Version geplant. Die Methode ist bereits im Interface definiert, jedoch als `[Obsolete]` markiert und wirft zur Laufzeit eine `NotImplementedException`. Der API-Endpunkt `POST /api/pdf/attachments/add` ist noch nicht implementiert.
|
||
|
||
---
|
||
|
||
### 3. PDF-Operationen
|
||
|
||
#### Zusammenführen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// pageRanges ist optional – null bedeutet "alle Seiten dieses Dokuments"
|
||
// Seitenbereich-Syntax: "1-3" = Seiten 1 bis 3, "2,4,6" = einzelne Seiten
|
||
var streams = new List<Stream>
|
||
{
|
||
File.OpenRead("teil1.pdf"),
|
||
File.OpenRead("teil2.pdf"),
|
||
File.OpenRead("teil3.pdf")
|
||
};
|
||
|
||
using var mergedStream = await Client.Operations.MergeAsync(
|
||
streams,
|
||
pageRanges: new List<string?> { "1-2", null, "3,5" }
|
||
);
|
||
|
||
foreach (var s in streams) await s.DisposeAsync();
|
||
|
||
await using var output = File.Create("zusammengefuehrt.pdf");
|
||
await mergedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim streams As New List(Of System.IO.Stream) From {
|
||
System.IO.File.OpenRead("teil1.pdf"),
|
||
System.IO.File.OpenRead("teil2.pdf"),
|
||
System.IO.File.OpenRead("teil3.pdf")
|
||
}
|
||
Dim pageRanges As New List(Of String) From {"1-2", Nothing, "3,5"}
|
||
|
||
Dim mergedStream = Await Client.Operations.MergeAsync(streams, pageRanges)
|
||
|
||
For Each s In streams
|
||
s.Dispose()
|
||
Next
|
||
|
||
Using output = System.IO.File.Create("zusammengefuehrt.pdf")
|
||
Await mergedStream.CopyToAsync(output)
|
||
End Using
|
||
mergedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$streams = [System.Collections.Generic.List[System.IO.Stream]]::new()
|
||
$streams.Add([System.IO.File]::OpenRead("teil1.pdf"))
|
||
$streams.Add([System.IO.File]::OpenRead("teil2.pdf"))
|
||
$streams.Add([System.IO.File]::OpenRead("teil3.pdf"))
|
||
|
||
$pageRanges = [System.Collections.Generic.List[string]]::new()
|
||
$pageRanges.Add("1-2")
|
||
$pageRanges.Add($null)
|
||
$pageRanges.Add("3,5")
|
||
|
||
$mergedStream = [DocumentService.Client.Client]::Operations.MergeAsync($streams, $pageRanges).GetAwaiter().GetResult()
|
||
|
||
foreach ($s in $streams) { $s.Dispose() }
|
||
|
||
$output = [System.IO.File]::Create("zusammengefuehrt.pdf")
|
||
$mergedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$mergedStream.Dispose()
|
||
```
|
||
|
||
#### Annotieren
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
using DocumentService.Application.Common.DTOs.Requests;
|
||
using DocumentService.Domain.Models.ValueObjects;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("dokument.pdf");
|
||
|
||
var request = new AddAnnotationBase64Request
|
||
{
|
||
Base64Pdf = string.Empty, // wird intern vom Client befüllt
|
||
AnnotationType = AnnotationType.TextMarkup,
|
||
PageNumber = 1,
|
||
X1 = 100,
|
||
Y1 = 200,
|
||
Width = 300,
|
||
Height = 20,
|
||
Color = "FFFF00", // Gelb (RGB-Hex)
|
||
TextMarkupStyle = TextMarkupStyle.Highlight,
|
||
Origin = AnnotationOrigin.TopLeft // Y-Achse von oben (UI-Koordinaten)
|
||
};
|
||
|
||
using var annotatedStream = await Client.Operations.AnnotateAsync(pdfBytes, request);
|
||
await using var output = File.Create("annotiert.pdf");
|
||
await annotatedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
Imports DocumentService.Application.Common.DTOs.Requests
|
||
Imports DocumentService.Domain.Models.ValueObjects
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("dokument.pdf")
|
||
|
||
Dim request As New AddAnnotationBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.AnnotationType = AnnotationType.TextMarkup,
|
||
.PageNumber = 1,
|
||
.X1 = 100,
|
||
.Y1 = 200,
|
||
.Width = 300,
|
||
.Height = 20,
|
||
.Color = "FFFF00",
|
||
.TextMarkupStyle = TextMarkupStyle.Highlight,
|
||
.Origin = AnnotationOrigin.TopLeft
|
||
}
|
||
|
||
Dim annotatedStream = Await Client.Operations.AnnotateAsync(pdfBytes, request)
|
||
Using output = System.IO.File.Create("annotiert.pdf")
|
||
Await annotatedStream.CopyToAsync(output)
|
||
End Using
|
||
annotatedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("dokument.pdf")
|
||
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddAnnotationBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.AnnotationType = [DocumentService.Domain.Models.ValueObjects.AnnotationType]::TextMarkup
|
||
$request.PageNumber = 1
|
||
$request.X1 = 100
|
||
$request.Y1 = 200
|
||
$request.Width = 300
|
||
$request.Height = 20
|
||
$request.Color = "FFFF00"
|
||
$request.TextMarkupStyle = [DocumentService.Domain.Models.ValueObjects.TextMarkupStyle]::Highlight
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::TopLeft
|
||
|
||
$annotatedStream = [DocumentService.Client.Client]::Operations.AnnotateAsync($pdfBytes, $request).GetAwaiter().GetResult()
|
||
$output = [System.IO.File]::Create("annotiert.pdf")
|
||
$annotatedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$annotatedStream.Dispose()
|
||
```
|
||
|
||
**Verfügbare Annotationstypen:**
|
||
|
||
| `AnnotationType` | Beschreibung |
|
||
|---|---|
|
||
| `TextMarkup` | Hervorhebung, Unterstreichung oder Durchstreichung (`TextMarkupStyle`) |
|
||
| `FreeText` | Freitextkommentar direkt auf der Seite |
|
||
| `StickyNote` | Klebezettel (Popup-Kommentar) |
|
||
| `Circle` | Kreismarkierung |
|
||
| `Square` | Rechteckmarkierung |
|
||
|
||
**Koordinatenursprung (`AnnotationOrigin`):**
|
||
|
||
| Wert | Bedeutung |
|
||
|---|---|
|
||
| `TopLeft` | Y-Achse von oben – entspricht UI-Koordinaten (z. B. aus einem Viewer) |
|
||
| `BottomLeft` | Y-Achse von unten – natives PDF-Koordinatensystem |
|
||
|
||
#### Stempel
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
using DocumentService.Application.Common.DTOs.Requests;
|
||
using DocumentService.Domain.Models.ValueObjects;
|
||
|
||
// Textstempel diagonal auf alle Seiten
|
||
using var pdfStream = File.OpenRead("dokument.pdf");
|
||
|
||
var request = new AddStampBase64Request
|
||
{
|
||
Base64Pdf = string.Empty, // wird intern vom Client befüllt
|
||
StampType = StampType.Text,
|
||
Text = "VERTRAULICH",
|
||
FontName = "Arial",
|
||
FontSize = 36,
|
||
Color = "FF0000", // Rot
|
||
Opacity = 0.4, // 40% Transparenz
|
||
Rotation = 45, // Grad gegen den Uhrzeigersinn
|
||
X = 150,
|
||
Y = 400,
|
||
Placement = StampPlacement.Foreground, // über dem Inhalt
|
||
Origin = AnnotationOrigin.BottomLeft
|
||
// PageNumbers = null → alle Seiten
|
||
};
|
||
|
||
using var stampedStream = await Client.Operations.StampAsync(pdfStream, request);
|
||
await using var output = File.Create("gestempelt.pdf");
|
||
await stampedStream.CopyToAsync(output);
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
Imports DocumentService.Application.Common.DTOs.Requests
|
||
Imports DocumentService.Domain.Models.ValueObjects
|
||
|
||
Dim pdfStream As New System.IO.FileStream("dokument.pdf", System.IO.FileMode.Open)
|
||
|
||
Dim request As New AddStampBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.StampType = StampType.Text,
|
||
.Text = "VERTRAULICH",
|
||
.FontName = "Arial",
|
||
.FontSize = 36,
|
||
.Color = "FF0000",
|
||
.Opacity = 0.4,
|
||
.Rotation = 45,
|
||
.X = 150,
|
||
.Y = 400,
|
||
.Placement = StampPlacement.Foreground,
|
||
.Origin = AnnotationOrigin.BottomLeft
|
||
}
|
||
|
||
Dim stampedStream = Await Client.Operations.StampAsync(pdfStream, request)
|
||
pdfStream.Dispose()
|
||
|
||
Using output = System.IO.File.Create("gestempelt.pdf")
|
||
Await stampedStream.CopyToAsync(output)
|
||
End Using
|
||
stampedStream.Dispose()
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfStream = [System.IO.File]::OpenRead("dokument.pdf")
|
||
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddStampBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.StampType = [DocumentService.Domain.Models.ValueObjects.StampType]::Text
|
||
$request.Text = "VERTRAULICH"
|
||
$request.FontName = "Arial"
|
||
$request.FontSize = 36
|
||
$request.Color = "FF0000"
|
||
$request.Opacity = 0.4
|
||
$request.Rotation = 45
|
||
$request.X = 150
|
||
$request.Y = 400
|
||
$request.Placement = [DocumentService.Domain.Models.ValueObjects.StampPlacement]::Foreground
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::BottomLeft
|
||
|
||
$stampedStream = [DocumentService.Client.Client]::Operations.StampAsync($pdfStream, $request).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
$output = [System.IO.File]::Create("gestempelt.pdf")
|
||
$stampedStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$stampedStream.Dispose()
|
||
```
|
||
|
||
```csharp
|
||
// Vordefinierter Stempel
|
||
var request = new AddStampBase64Request
|
||
{
|
||
Base64Pdf = string.Empty,
|
||
StampType = StampType.Predefined,
|
||
PredefinedStamp = PredefinedStampType.Approved,
|
||
X = 400,
|
||
Y = 700,
|
||
Placement = StampPlacement.Foreground,
|
||
Origin = AnnotationOrigin.BottomLeft
|
||
};
|
||
```
|
||
|
||
```vbnet
|
||
' Vordefinierter Stempel
|
||
Dim request As New AddStampBase64Request With {
|
||
.Base64Pdf = String.Empty,
|
||
.StampType = StampType.Predefined,
|
||
.PredefinedStamp = PredefinedStampType.Approved,
|
||
.X = 400,
|
||
.Y = 700,
|
||
.Placement = StampPlacement.Foreground,
|
||
.Origin = AnnotationOrigin.BottomLeft
|
||
}
|
||
```
|
||
|
||
```powershell
|
||
# Vordefinierter Stempel
|
||
$request = [DocumentService.Application.Common.DTOs.Requests.AddStampBase64Request]::new()
|
||
$request.Base64Pdf = ""
|
||
$request.StampType = [DocumentService.Domain.Models.ValueObjects.StampType]::Predefined
|
||
$request.PredefinedStamp = [DocumentService.Domain.Models.ValueObjects.PredefinedStampType]::Approved
|
||
$request.X = 400
|
||
$request.Y = 700
|
||
$request.Placement = [DocumentService.Domain.Models.ValueObjects.StampPlacement]::Foreground
|
||
$request.Origin = [DocumentService.Domain.Models.ValueObjects.AnnotationOrigin]::BottomLeft
|
||
```
|
||
|
||
**Verfügbare Stempeltypen:**
|
||
|
||
| `StampType` | Beschreibung |
|
||
|---|---|
|
||
| `Text` | Eigener Textstempel mit freier Formatierung |
|
||
| `Image` | Bildstempel – `Base64Image` als PNG/JPEG in Base64 |
|
||
| `Predefined` | Vorgefertigter Stempel: `Confidential`, `Approved`, `Draft`, `Void`, `ForReview` |
|
||
|
||
---
|
||
|
||
### 4. Schweizer QR-Code-Extraktion
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
// Geparste Rechnungsstruktur gemäß Swiss QR Bill Standard 2.0
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("qr-rechnung.pdf");
|
||
var result = await Client.SwissQrCode.ExtractAsync(pdfBytes, raw: false);
|
||
|
||
Console.WriteLine($"Betrag: {result.Bill?.Amount} {result.Bill?.Currency}");
|
||
Console.WriteLine($"Konto: {result.Bill?.Account}");
|
||
Console.WriteLine($"Empfänger: {result.Bill?.Creditor?.Name}, {result.Bill?.Creditor?.Town}");
|
||
Console.WriteLine($"Referenz: {result.Bill?.Reference}");
|
||
Console.WriteLine($"Mitteilung:{result.Bill?.UnstructuredMessage}");
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("qr-rechnung.pdf")
|
||
Dim result = Await Client.SwissQrCode.ExtractAsync(pdfBytes, raw:=False)
|
||
|
||
Console.WriteLine($"Betrag: {result.Bill?.Amount} {result.Bill?.Currency}")
|
||
Console.WriteLine($"Konto: {result.Bill?.Account}")
|
||
Console.WriteLine($"Empfänger: {result.Bill?.Creditor?.Name}, {result.Bill?.Creditor?.Town}")
|
||
Console.WriteLine($"Referenz: {result.Bill?.Reference}")
|
||
Console.WriteLine($"Mitteilung:{result.Bill?.UnstructuredMessage}")
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("qr-rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::SwissQrCode.ExtractAsync($pdfBytes, $false).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Betrag: $($result.Bill.Amount) $($result.Bill.Currency)"
|
||
Write-Host "Konto: $($result.Bill.Account)"
|
||
Write-Host "Empfaenger:$($result.Bill.Creditor.Name), $($result.Bill.Creditor.Town)"
|
||
Write-Host "Referenz: $($result.Bill.Reference)"
|
||
Write-Host "Mitteilung:$($result.Bill.UnstructuredMessage)"
|
||
```
|
||
|
||
---
|
||
|
||
#### Rohe QR-Textzeilen
|
||
|
||
```csharp
|
||
// Ohne Parsing – direkt aus dem QR-Code-Inhalt
|
||
using var pdfStream = File.OpenRead("qr-rechnung.pdf");
|
||
var result = await Client.SwissQrCode.ExtractAsync(pdfStream, raw: true);
|
||
|
||
foreach (var line in result.RawLines)
|
||
Console.WriteLine(line);
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfStream As New System.IO.FileStream("qr-rechnung.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.SwissQrCode.ExtractAsync(pdfStream, raw:=True)
|
||
pdfStream.Dispose()
|
||
|
||
For Each line In result.RawLines
|
||
Console.WriteLine(line)
|
||
Next
|
||
```
|
||
|
||
```powershell
|
||
$pdfStream = [System.IO.File]::OpenRead("qr-rechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::SwissQrCode.ExtractAsync($pdfStream, $true).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
foreach ($line in $result.RawLines) { Write-Host $line }
|
||
```
|
||
|
||
> **Pflichtanforderung laut Standard:** Der QR-Code muss sich auf der **letzten Seite** des PDFs befinden. PDFs, bei denen der QR-Code auf einer anderen Seite liegt, werden nicht erkannt.
|
||
|
||
---
|
||
|
||
### 5. ZUGFeRD-Rechnungen
|
||
|
||
#### ZUGFeRD-Einbettung prüfen
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("eingangsrechnung.pdf");
|
||
var check = await Client.Zugferd.CheckAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Enthält ZUGFeRD: {check.HasZugferd}");
|
||
|
||
if (check.HasZugferd)
|
||
{
|
||
Console.WriteLine($"XML-Dateiname: {check.ZugferdFileName}");
|
||
Console.WriteLine($"Dateigröße: {check.ZugferdFileSize} Bytes");
|
||
Console.WriteLine($"MIME-Typ: {check.ZugferdMimeType}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("eingangsrechnung.pdf")
|
||
Dim check = Await Client.Zugferd.CheckAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Enthält ZUGFeRD: {check.HasZugferd}")
|
||
|
||
If check.HasZugferd Then
|
||
Console.WriteLine($"XML-Dateiname: {check.ZugferdFileName}")
|
||
Console.WriteLine($"Dateigröße: {check.ZugferdFileSize} Bytes")
|
||
Console.WriteLine($"MIME-Typ: {check.ZugferdMimeType}")
|
||
End If
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("eingangsrechnung.pdf")
|
||
$check = [DocumentService.Client.Client]::Zugferd.CheckAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Enthaelt ZUGFeRD: $($check.HasZugferd)"
|
||
|
||
if ($check.HasZugferd) {
|
||
Write-Host "XML-Dateiname: $($check.ZugferdFileName)"
|
||
Write-Host "Dateigroesse: $($check.ZugferdFileSize) Bytes"
|
||
Write-Host "MIME-Typ: $($check.ZugferdMimeType)"
|
||
}
|
||
```
|
||
|
||
#### ZUGFeRD-XML als Stream extrahieren
|
||
|
||
```csharp
|
||
using var pdfStream = File.OpenRead("eingangsrechnung.pdf");
|
||
using var xmlStream = await Client.Zugferd.ExtractAsync(pdfStream);
|
||
|
||
await using var output = File.Create("factur-x.xml");
|
||
await xmlStream.CopyToAsync(output);
|
||
Console.WriteLine("ZUGFeRD-XML gespeichert.");
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfStream As New System.IO.FileStream("eingangsrechnung.pdf", System.IO.FileMode.Open)
|
||
Dim xmlStream = Await Client.Zugferd.ExtractAsync(pdfStream)
|
||
pdfStream.Dispose()
|
||
|
||
Using output = System.IO.File.Create("factur-x.xml")
|
||
Await xmlStream.CopyToAsync(output)
|
||
End Using
|
||
xmlStream.Dispose()
|
||
Console.WriteLine("ZUGFeRD-XML gespeichert.")
|
||
```
|
||
|
||
```powershell
|
||
$pdfStream = [System.IO.File]::OpenRead("eingangsrechnung.pdf")
|
||
$xmlStream = [DocumentService.Client.Client]::Zugferd.ExtractAsync($pdfStream).GetAwaiter().GetResult()
|
||
$pdfStream.Dispose()
|
||
|
||
$output = [System.IO.File]::Create("factur-x.xml")
|
||
$xmlStream.CopyTo($output)
|
||
$output.Dispose()
|
||
$xmlStream.Dispose()
|
||
Write-Host "ZUGFeRD-XML gespeichert."
|
||
```
|
||
|
||
#### ZUGFeRD-XML als strukturiertes Ergebnis
|
||
|
||
```csharp
|
||
byte[] pdfBytes = await File.ReadAllBytesAsync("eingangsrechnung.pdf");
|
||
var result = await Client.Zugferd.ExtractAsResultAsync(pdfBytes);
|
||
|
||
Console.WriteLine($"Dateiname: {result.FileName}");
|
||
Console.WriteLine($"Größe: {result.FileSize} Bytes");
|
||
Console.WriteLine($"XML-Inhalt: {result.XmlContent[..500]}...");
|
||
|
||
// XML weiterverarbeiten, z. B. mit System.Xml.Linq
|
||
var doc = XDocument.Parse(result.XmlContent);
|
||
```
|
||
|
||
```vbnet
|
||
Dim pdfBytes = System.IO.File.ReadAllBytes("eingangsrechnung.pdf")
|
||
Dim result = Await Client.Zugferd.ExtractAsResultAsync(pdfBytes)
|
||
|
||
Console.WriteLine($"Dateiname: {result.FileName}")
|
||
Console.WriteLine($"Größe: {result.FileSize} Bytes")
|
||
Console.WriteLine($"XML-Inhalt: {result.XmlContent.Substring(0, 500)}...")
|
||
|
||
' XML weiterverarbeiten
|
||
Dim doc = System.Xml.Linq.XDocument.Parse(result.XmlContent)
|
||
```
|
||
|
||
```powershell
|
||
$pdfBytes = [System.IO.File]::ReadAllBytes("eingangsrechnung.pdf")
|
||
$result = [DocumentService.Client.Client]::Zugferd.ExtractAsResultAsync($pdfBytes).GetAwaiter().GetResult()
|
||
|
||
Write-Host "Dateiname: $($result.FileName)"
|
||
Write-Host "Groesse: $($result.FileSize) Bytes"
|
||
Write-Host "XML-Inhalt: $($result.XmlContent.Substring(0, 500))..."
|
||
|
||
# XML weiterverarbeiten
|
||
$doc = [System.Xml.Linq.XDocument]::Parse($result.XmlContent)
|
||
```
|
||
|
||
---
|
||
|
||
### 6. PDF-Konvertierung *(geplant – noch nicht verfügbar)*
|
||
|
||
`IPdfConversionClient` ist vollständig definiert und über `Client.Conversion` erreichbar. Alle Methoden sind jedoch als `[Obsolete]` markiert und werfen zur Laufzeit `NotImplementedException`, da die API-Endpunkte serverseitig noch nicht implementiert sind.
|
||
|
||
**Geplante Endpunkte (Phase 3):**
|
||
- `POST /api/pdf/conversion/to-pdfa` – beliebiges PDF in PDF/A konvertieren
|
||
- `POST /api/pdf/conversion/from-pdfa` – PDF/A-Einschränkungen entfernen
|
||
|
||
```csharp
|
||
// NICHT VERWENDEN – wirft NotImplementedException
|
||
#pragma warning disable CS0618
|
||
using var pdfAStream = await Client.Conversion.ToPdfAAsync(pdfStream, pdfALevel: "PDF/A-3b");
|
||
#pragma warning restore CS0618
|
||
```
|
||
|
||
```vbnet
|
||
' NICHT VERWENDEN – wirft NotImplementedException
|
||
#Disable Warning BC40000
|
||
Dim pdfAStream = Await Client.Conversion.ToPdfAAsync(pdfStream, pdfALevel:="PDF/A-3b")
|
||
#Enable Warning BC40000
|
||
```
|
||
|
||
```powershell
|
||
# NICHT VERWENDEN – wirft NotImplementedException
|
||
$pdfAStream = [DocumentService.Client.Client]::Conversion.ToPdfAAsync($pdfStream, "PDF/A-3b").GetAwaiter().GetResult()
|
||
```
|
||
|
||
Sobald die Endpunkte auf dem Server bereitgestellt werden, entfällt das `[Obsolete]`-Attribut und die Methoden sind ohne weiteren Anpassungsbedarf auf Clientseite verwendbar.
|
||
|
||
---
|
||
|
||
## API-Endpunkte (Übersicht)
|
||
|
||
| **Client-Eigenschaft** | **Methode** | **API-Endpunkt** | **Status** | **Beschreibung** |
|
||
|---|---|---|---|---|
|
||
| `Client.Validation` | `ValidateAsync()` | `POST /api/pdf/validation/validate` | ✅ Verfügbar | PDF validieren, Metadaten abrufen |
|
||
| `Client.Validation` | `ValidatePdfAAsync()` | `POST /api/pdf/validation/validate-pdfa` | ✅ Verfügbar | PDF/A-Konformität prüfen |
|
||
| `Client.Attachment` | `CheckAsync()` | `POST /api/pdf/attachments/check` | ✅ Verfügbar | Eingebettete Anhänge erkennen |
|
||
| `Client.Attachment` | `ExtractSwissQrCodeAsync()` | `POST /api/pdf/attachments/extract` | ✅ Verfügbar | Anhänge extrahieren als `Dictionary<string, Stream>` |
|
||
| `Client.Attachment` | `AddAsync()` | `POST /api/pdf/attachments/add` | ⏳ Geplant | Anhänge in PDF/A-3 einbetten |
|
||
| `Client.Operations` | `MergeAsync()` | `POST /api/pdf/operations/merge` | ✅ Verfügbar | Mehrere PDFs zusammenführen |
|
||
| `Client.Operations` | `AnnotateAsync()` | `POST /api/pdf/operations/annotate` | ✅ Verfügbar | Annotationen hinzufügen |
|
||
| `Client.Operations` | `StampAsync()` | `POST /api/pdf/operations/stamp` | ✅ Verfügbar | Text-/Bildstempel hinzufügen |
|
||
| `Client.SwissQrCode` | `ExtractSwissQrCodeAsync()` | `POST /api/pdf/qr-code/extract-swiss` | ✅ Verfügbar | Schweizer QR-Code-Daten extrahieren |
|
||
| `Client.Zugferd` | `CheckAsync()` | `POST /api/pdf/zugferd/has-zugferd` | ✅ Verfügbar | ZUGFeRD-Einbettung prüfen |
|
||
| `Client.Zugferd` | `ExtractSwissQrCodeAsync()` | `POST /api/pdf/zugferd/extract` | ✅ Verfügbar | ZUGFeRD-XML als Stream |
|
||
| `Client.Zugferd` | `ExtractAsResultAsync()` | `POST /api/pdf/zugferd/extract` | ✅ Verfügbar | ZUGFeRD-XML mit Metadaten als Objekt |
|
||
| `Client.Conversion` | `ToPdfAAsync()` | `POST /api/pdf/conversion/to-pdfa` | ⏳ Geplant | PDF zu PDF/A konvertieren |
|
||
| `Client.Conversion` | `FromPdfAAsync()` | `POST /api/pdf/conversion/from-pdfa` | ⏳ Geplant | PDF/A-Einschränkungen aufheben |
|
||
| `Client.Workflows` | `ExtractSwissQrCodeAsync()` | — | ✅ Verfügbar | Validierung + Swiss QR Code Extraktion in einem Schritt |
|
||
|
||
> **⏳ Geplant:** Der Client-Code ist vorhanden und kompiliert fehlerfrei. Ein Aufruf zur Laufzeit wirft jedoch `NotImplementedException`, da der serverseitige Endpunkt noch nicht existiert. Die Methoden sind mit `[Obsolete]` markiert, sodass der Compiler eine Build-Warnung ausgibt.
|
||
|
||
---
|
||
|
||
## Fehlerbehandlung
|
||
|
||
```csharp
|
||
using DocumentService.Client;
|
||
|
||
try
|
||
{
|
||
using var stream = File.OpenRead("dokument.pdf");
|
||
var result = await Client.Validation.ValidateAsync(stream);
|
||
Console.WriteLine($"Seiten: {result.PageCount}");
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("not configured"))
|
||
{
|
||
// Client.Configure() wurde nicht aufgerufen
|
||
Console.WriteLine("Client ist nicht initialisiert.");
|
||
}
|
||
catch (HttpRequestException ex)
|
||
{
|
||
// Netzwerkfehler oder HTTP 4xx/5xx-Antwort vom Server
|
||
Console.WriteLine($"HTTP-Fehler: {ex.Message}");
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
// API hat null zurückgegeben (unerwartetes Serververhalten)
|
||
Console.WriteLine($"Unerwartete API-Antwort: {ex.Message}");
|
||
}
|
||
catch (NotImplementedException ex)
|
||
{
|
||
// Geplanter Endpunkt aufgerufen – noch nicht auf dem Server verfügbar
|
||
Console.WriteLine($"Endpunkt noch nicht implementiert: {ex.Message}");
|
||
}
|
||
```
|
||
|
||
```vbnet
|
||
Imports DocumentService.Client
|
||
|
||
Try
|
||
Dim stream As New System.IO.FileStream("dokument.pdf", System.IO.FileMode.Open)
|
||
Dim result = Await Client.Validation.ValidateAsync(stream)
|
||
stream.Dispose()
|
||
Console.WriteLine($"Seiten: {result.PageCount}")
|
||
Catch ex As InvalidOperationException When ex.Message.Contains("not configured")
|
||
Console.WriteLine("Client ist nicht initialisiert.")
|
||
Catch ex As System.Net.Http.HttpRequestException
|
||
Console.WriteLine($"HTTP-Fehler: {ex.Message}")
|
||
Catch ex As InvalidOperationException
|
||
Console.WriteLine($"Unerwartete API-Antwort: {ex.Message}")
|
||
Catch ex As NotImplementedException
|
||
Console.WriteLine($"Endpunkt noch nicht implementiert: {ex.Message}")
|
||
End Try
|
||
```
|
||
|
||
```powershell
|
||
Add-Type -Path "DocumentService.Client.dll"
|
||
|
||
try {
|
||
$stream = [System.IO.File]::OpenRead("dokument.pdf")
|
||
$result = [DocumentService.Client.Client]::Validation.ValidateAsync($stream).GetAwaiter().GetResult()
|
||
$stream.Dispose()
|
||
Write-Host "Seiten: $($result.PageCount)"
|
||
}
|
||
catch [System.InvalidOperationException] {
|
||
if ($_.Exception.Message -like "*not configured*") {
|
||
Write-Host "Client ist nicht initialisiert."
|
||
} else {
|
||
Write-Host "Unerwartete API-Antwort: $($_.Exception.Message)"
|
||
}
|
||
}
|
||
catch [System.Net.Http.HttpRequestException] {
|
||
Write-Host "HTTP-Fehler: $($_.Exception.Message)"
|
||
}
|
||
catch [System.NotImplementedException] {
|
||
Write-Host "Endpunkt noch nicht implementiert: $($_.Exception.Message)"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Lizenz
|
||
|
||
Copyright © 2026 Digital Data GmbH. Alle Rechte vorbehalten.
|