Files
DocumentService/DocumentService.Client/README.md
TekH 189622c476 Refactor: Standardize method names across clients
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.
2026-08-31 03:40:43 +02:00

1175 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.